{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9fbf6701",
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cd031163",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import gc\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import re\n",
    "import time\n",
    "from scipy import stats\n",
    "import matplotlib.pyplot as plt\n",
    "import category_encoders as ce\n",
    "import networkx as nx\n",
    "import pickle\n",
    "import lightgbm as lgb\n",
    "import catboost as cat\n",
    "import xgboost as xgb\n",
    "from datetime import timedelta\n",
    "from gensim.models import Word2Vec\n",
    "from io import StringIO\n",
    "from tqdm import tqdm\n",
    "from lightgbm import LGBMClassifier\n",
    "from lightgbm import log_evaluation, early_stopping\n",
    "from sklearn.metrics import roc_curve\n",
    "from scipy.stats import chi2_contingency, pearsonr\n",
    "from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n",
    "from sklearn.feature_extraction import FeatureHasher\n",
    "from sklearn.model_selection import StratifiedKFold, KFold, train_test_split, GridSearchCV\n",
    "from category_encoders import TargetEncoder\n",
    "from sklearn.decomposition import TruncatedSVD\n",
    "from autogluon.tabular import TabularDataset, TabularPredictor, FeatureMetadata\n",
    "from autogluon.features.generators import AsTypeFeatureGenerator, BulkFeatureGenerator, DropUniqueFeatureGenerator, FillNaFeatureGenerator, PipelineFeatureGenerator\n",
    "from autogluon.features.generators import CategoryFeatureGenerator, IdentityFeatureGenerator, AutoMLPipelineFeatureGenerator\n",
    "from autogluon.common.features.types import R_INT, R_FLOAT\n",
    "from autogluon.core.metrics import make_scorer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb74bb1e",
   "metadata": {},
   "source": [
    "# 特征工程"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63ea38d9",
   "metadata": {},
   "source": [
    "## 数据导入通用函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8298a200",
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_data_from_directory(directory):\n",
    "    \"\"\"\n",
    "    遍历目录加载所有CSV文件，将其作为独立的DataFrame变量\n",
    "\n",
    "    参数:\n",
    "    - directory: 输入的数据路径\n",
    "    \n",
    "    返回:\n",
    "    - 含有数据集名称的列表\n",
    "    \"\"\"\n",
    "    dataset_names = []\n",
    "    for filename in os.listdir(directory):\n",
    "        if filename.endswith(\".csv\"):\n",
    "            dataset_name = os.path.splitext(filename)[0] + '_data' # 获取文件名作为变量名\n",
    "            file_path = os.path.join(directory, filename)  # 完整的文件路径\n",
    "            globals()[dataset_name] = pd.read_csv(file_path)  # 将文件加载为DataFrame并赋值给全局变量\n",
    "            dataset_names.append(dataset_name)\n",
    "            print(f\"数据集 {dataset_name} 已加载为 DataFrame\")\n",
    "\n",
    "    return dataset_names"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b99c9f1",
   "metadata": {},
   "source": [
    "# 导入数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5d006341",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集 AGET_PAY_data 已加载为 DataFrame\n",
      "数据集 ASSET_data 已加载为 DataFrame\n",
      "数据集 CCD_TR_DTL_data 已加载为 DataFrame\n",
      "数据集 MB_PAGEVIEW_DTL_data 已加载为 DataFrame\n",
      "数据集 MB_QRYTRNFLW_data 已加载为 DataFrame\n",
      "数据集 MB_TRNFLW_data 已加载为 DataFrame\n",
      "数据集 NATURE_data 已加载为 DataFrame\n",
      "数据集 PROD_HOLD_data 已加载为 DataFrame\n",
      "数据集 TARGET_data 已加载为 DataFrame\n",
      "数据集 TARGET_VALID_data 已加载为 DataFrame\n",
      "数据集 TR_APS_DTL_data 已加载为 DataFrame\n",
      "数据集 TR_IBTF_data 已加载为 DataFrame\n",
      "数据集 TR_TPAY_data 已加载为 DataFrame\n"
     ]
    }
   ],
   "source": [
    "train_load_dt = '../DATA'\n",
    "train_data_name = load_data_from_directory(train_load_dt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b45d0db1",
   "metadata": {},
   "source": [
    "# 特征工程"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c961ece6",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "5a7770b7",
   "metadata": {},
   "source": [
    "## 📊 特征工程总体设计\n",
    "\n",
    "### 一、代发工资信息表(AGET_PAY)特征工程策略\n",
    "\n",
    "参考2023年公私联动第一名方案，核心特征包括：\n",
    "\n",
    "**1. 基础统计特征**\n",
    "- 代发笔数统计（总计、月度、周度）\n",
    "- 代发金额统计（总和、均值、标准差、中位数、最大最小值）\n",
    "- 单位类型编码特征\n",
    "- 省份编码特征\n",
    "\n",
    "**2. RFM特征体系**\n",
    "- **R (Recency)**: 最近一笔代发距今天数、最大金额代发距今天数\n",
    "- **F (Frequency)**: 代发频率、月均代发次数、代发规律性\n",
    "- **M (Monetary)**: 代发金额分布、金额稳定性、金额增长趋势\n",
    "\n",
    "**3. 时间序列特征**\n",
    "- 代发间隔统计（最大/最小/均值/标准差）\n",
    "- 代发规律性（间隔变异系数）\n",
    "- 跨月代发稳定性\n",
    "- 代发日期模式（月初/月中/月末）\n",
    "\n",
    "**4. 单位维度特征**\n",
    "- 代发单位数量\n",
    "- 单位集中度（主要单位占比）\n",
    "- 单位稳定性（跨月单位留存率）\n",
    "- 单位类型分布\n",
    "\n",
    "**5. 交叉特征**\n",
    "- 代发金额与活期流出的关联（当天流失率）\n",
    "- 代发后资金留存天数\n",
    "- 代发金额与存款余额的比值\n",
    "\n",
    "---\n",
    "\n",
    "### 二、信用卡交易流水表(CCD_TR_DTL)特征工程策略\n",
    "\n",
    "参考2024年营销响应第一名方案，核心特征包括：\n",
    "\n",
    "**1. 基础消费特征**\n",
    "- 交易笔数统计（总计、月度、周度、日度）\n",
    "- 交易金额统计（总和、均值、标准差、偏度、峰度）\n",
    "- 笔均交易金额（总体、月度、周度）\n",
    "\n",
    "**2. 消费行为特征**\n",
    "- 消费频率特征（日均笔数、周均笔数）\n",
    "- 消费金额分布（四分位数、变异系数）\n",
    "- 大额消费占比（>均值+2std的笔数占比）\n",
    "- 小额消费占比（<均值的笔数占比）\n",
    "\n",
    "**3. 时间模式特征**\n",
    "- 最近交易距今天数\n",
    "- 交易活跃天数统计\n",
    "- 交易间隔统计（均值、标准差、最大、最小）\n",
    "- 交易时间稳定性（间隔变异系数）\n",
    "- 周末/工作日消费差异\n",
    "\n",
    "**4. 商户类型特征**\n",
    "- 商户类型多样性（nunique）\n",
    "- 商户集中度（top3商户交易占比）\n",
    "- 商户忠诚度（重复消费商户占比）\n",
    "- 商户类型偏好（各类型交易占比）\n",
    "\n",
    "**5. 消费趋势特征**\n",
    "- 月度消费金额趋势（环比增长率）\n",
    "- 消费频率趋势（环比变化）\n",
    "- 消费金额波动性（月度标准差）\n",
    "- 消费习惯稳定性\n",
    "\n",
    "**6. 风险相关特征**\n",
    "- 大额异常交易次数\n",
    "- 交易金额突变次数\n",
    "- 夜间交易占比\n",
    "- 跨区域交易特征\n",
    "\n",
    "**7. 交叉特征**\n",
    "- 信用卡消费与收入（代发工资）的比值\n",
    "- 信用卡消费与存款余额的比值\n",
    "- 消费/还款比率（如果有还款数据）\n",
    "\n",
    "---\n",
    "\n",
    "### 三、特征工程实现要点\n",
    "\n",
    "1. **数据预处理**：日期格式转换、距今天数计算、月份划分\n",
    "2. **分组聚合**：按CUST_NO、月份、周、日等维度聚合\n",
    "3. **统计函数**：mean, sum, std, median, max, min, count, nunique, skew, kurt\n",
    "4. **透视转换**：将类别特征展开为多列特征\n",
    "5. **特征保存**：使用pickle格式保存到feature目录"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3719b46f",
   "metadata": {},
   "source": [
    "## 🔧 通用函数定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "fbc4e552",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ 通用函数定义完成\n"
     ]
    }
   ],
   "source": [
    "# ==================== 日期转换函数 ====================\n",
    "\n",
    "def get_days_to_now(df, date_col):\n",
    "    \"\"\"\n",
    "    将日期列转换为距今天数特征\n",
    "    \n",
    "    参数:\n",
    "    - df: 数据框\n",
    "    - date_col: 日期列名\n",
    "    \n",
    "    返回:\n",
    "    - 添加了时间特征的数据框\n",
    "    \"\"\"\n",
    "    # 日期转换\n",
    "    df[\"date\"] = pd.to_datetime(df[date_col], format=\"%Y%m%d\")\n",
    "    \n",
    "    # 计算距最大日期的天数\n",
    "    max_date = df[\"date\"].max()\n",
    "    df_days_to_now = (max_date - df[\"date\"]).dt.days\n",
    "    \n",
    "    # 添加时间维度特征\n",
    "    df[\"date_months_to_now\"] = df_days_to_now // 31  # 距今月数(0, 1, 2对应最近3个月)\n",
    "    df[\"date_weeks_to_now\"] = df_days_to_now // 7    # 距今周数\n",
    "    df[\"date_days_to_now\"] = df_days_to_now          # 距今天数\n",
    "    \n",
    "    print(f\"✅ 日期转换完成:\")\n",
    "    print(f\"   交易日期范围: {df['date'].min().date()} ~ {df['date'].max().date()}\")\n",
    "    print(f\"   距今天数范围: {df['date_days_to_now'].min()} ~ {df['date_days_to_now'].max()}\")\n",
    "    print(f\"   月份分布:\\n{df['date_months_to_now'].value_counts().sort_index()}\")\n",
    "    \n",
    "    return df\n",
    "\n",
    "# ==================== 通用聚合函数 ====================\n",
    "\n",
    "def get_dense_features(df, col, stat):\n",
    "    \"\"\"按客户ID聚合数值特征\"\"\"\n",
    "    if stat == \"kurt\":\n",
    "        f_stat = lambda x: x.kurt()\n",
    "    elif stat == \"quantile_1_4\":\n",
    "        f_stat = lambda x: x.quantile(0.25)\n",
    "    elif stat == \"quantile_1_2\":\n",
    "        f_stat = lambda x: x.quantile(0.5)\n",
    "    elif stat == \"quantile_3_4\":\n",
    "        f_stat = lambda x: x.quantile(0.75)\n",
    "    else:\n",
    "        f_stat = stat\n",
    "    \n",
    "    group_df = df.groupby(['CUST_NO'])[col].agg(f_stat).reset_index()\n",
    "    group_df.columns = ['CUST_NO', 'CUST_NO_'+'{}_'.format(col)+stat]\n",
    "    return group_df\n",
    "\n",
    "def get_all_dense_features(df_fea, df_to_groupby, stats):\n",
    "    \"\"\"批量生成数值型特征的统计量\"\"\"\n",
    "    dense_col = [col for col in df_to_groupby.columns if col != \"CUST_NO\"]\n",
    "    for col in tqdm(dense_col, desc=\"生成数值特征\"):\n",
    "        for stat in stats:\n",
    "            df_fea = df_fea.merge(get_dense_features(df_to_groupby, col, stat), on='CUST_NO', how='left')\n",
    "    return df_fea\n",
    "\n",
    "def get_id_category_features(df_fea, df_to_groupby, fea1, fea2, stat):\n",
    "    \"\"\"\n",
    "    按客户ID和类别特征聚合\n",
    "    fea1: 类别特征名(如交易代码、渠道)\n",
    "    fea2: 要聚合的数值特征名\n",
    "    stat: 统计函数\n",
    "    \"\"\"\n",
    "    tmp = df_to_groupby.groupby(['CUST_NO', fea1])[fea2].agg(\n",
    "        stat if stat != \"kurt\" else lambda x: x.kurt()\n",
    "    ).to_frame(\n",
    "        '_'.join(['CUST_NO', fea1, fea2, stat])\n",
    "    ).reset_index()\n",
    "    \n",
    "    # 透视表: 将类别特征展开为多列\n",
    "    df_tmp = pd.pivot(data=tmp, index='CUST_NO', columns=fea1, values='_'.join(['CUST_NO', fea1, fea2, stat]))\n",
    "    new_fea_cols = ['_'.join(['CUST_NO', fea1, fea2, stat, str(col)]) for col in df_tmp.columns]\n",
    "    df_tmp.columns = new_fea_cols\n",
    "    df_tmp.reset_index(inplace=True)\n",
    "        \n",
    "    if stat == 'count':\n",
    "        df_tmp = df_tmp.fillna(0)\n",
    "        \n",
    "    # 去掉全NaN列\n",
    "    valid_cols = []\n",
    "    for col in df_tmp.columns:\n",
    "        if not df_tmp[col].isna().all():\n",
    "            valid_cols.append(col)\n",
    "            \n",
    "    df_fea = df_fea.merge(df_tmp[valid_cols], on='CUST_NO', how='left')\n",
    "    return df_fea, new_fea_cols \n",
    "\n",
    "def get_all_id_category_features(df_fea, df_to_groupby, fea1, fea2, stats):\n",
    "    \"\"\"批量生成类别特征交叉统计\"\"\"\n",
    "    all_new_fea_cols = []\n",
    "    for stat in tqdm(stats, desc=f\"生成{fea1}分组特征\"):\n",
    "        df_fea, new_fea_cols = get_id_category_features(df_fea, df_to_groupby, fea1, fea2, stat)\n",
    "        all_new_fea_cols += new_fea_cols\n",
    "    return df_fea, all_new_fea_cols\n",
    "\n",
    "def get_division_features(df1, df2, col1, col2, eps=1e-6):\n",
    "    \"\"\"生成除法特征(比值特征)\"\"\"\n",
    "    tmp = pd.merge(df1, df2, how=\"left\", on=\"CUST_NO\")\n",
    "    new_feature_name = '_'.join([col1, \"div\", col2])\n",
    "    tmp[new_feature_name] = tmp[col1] / (tmp[col2] + eps)\n",
    "    feature_name = [\"CUST_NO\", new_feature_name]\n",
    "    return tmp[feature_name]\n",
    "\n",
    "print(\"✅ 通用函数定义完成\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3cfee529",
   "metadata": {},
   "source": [
    "## 💰 代发工资信息表(AGET_PAY)特征工程\n",
    "\n",
    "### 策略说明\n",
    "基于2023年公私联动第一名方案的特征设计：\n",
    "1. 类别特征编码（单位类型、省份）\n",
    "2. 基础统计特征（笔数、金额统计）\n",
    "3. RFM特征（最近性、频率、金额）\n",
    "4. 时间序列特征（间隔统计、规律性）\n",
    "5. 单位维度特征（单位数、集中度、稳定性）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "409b1073",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "代发工资数据原始形状: (2174, 6)\n",
      "字段: ['DATE', 'CUST_NO', 'AGEN_CUSNO', 'TR_AMT', 'PROV_CD', 'UNIT_TYP_CD']\n",
      "客户数: 530\n",
      "\n",
      "数据样例:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATE</th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>AGEN_CUSNO</th>\n",
       "      <th>TR_AMT</th>\n",
       "      <th>PROV_CD</th>\n",
       "      <th>UNIT_TYP_CD</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20250402</td>\n",
       "      <td>69f164a5ef15866d755bb91dcf1ba9b1</td>\n",
       "      <td>7418a3de35c7fb917d8f2a7bc1815b11</td>\n",
       "      <td>11931.82</td>\n",
       "      <td>d9d4f495e875a2e075a1a4a6e1b9770f</td>\n",
       "      <td>1d7c2aae840867027b7edd17b6aaa0e9</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20250402</td>\n",
       "      <td>92bde07ed2023077e703e98ddee3c843</td>\n",
       "      <td>0f1b9608fca0d25607a9bf2647b8392a</td>\n",
       "      <td>5842.47</td>\n",
       "      <td>d9d4f495e875a2e075a1a4a6e1b9770f</td>\n",
       "      <td>5f0ad4db43d8723d18169b2e4817a160</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20250402</td>\n",
       "      <td>a4042e8a5d05800d9b8e9993d6514448</td>\n",
       "      <td>0f1b9608fca0d25607a9bf2647b8392a</td>\n",
       "      <td>9074.06</td>\n",
       "      <td>d9d4f495e875a2e075a1a4a6e1b9770f</td>\n",
       "      <td>5f0ad4db43d8723d18169b2e4817a160</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20250402</td>\n",
       "      <td>1080209bc15196a8bfdc48f002c65787</td>\n",
       "      <td>7418a3de35c7fb917d8f2a7bc1815b11</td>\n",
       "      <td>12237.57</td>\n",
       "      <td>d9d4f495e875a2e075a1a4a6e1b9770f</td>\n",
       "      <td>1d7c2aae840867027b7edd17b6aaa0e9</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20250402</td>\n",
       "      <td>564c71ba76346d2686c2124b088d0271</td>\n",
       "      <td>5f0e8c23f558e1470985cda991b8a796</td>\n",
       "      <td>13824.00</td>\n",
       "      <td>d9d4f495e875a2e075a1a4a6e1b9770f</td>\n",
       "      <td>3b777b775721dfa8d36de2a320a03e53</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       DATE                           CUST_NO  \\\n",
       "0  20250402  69f164a5ef15866d755bb91dcf1ba9b1   \n",
       "1  20250402  92bde07ed2023077e703e98ddee3c843   \n",
       "2  20250402  a4042e8a5d05800d9b8e9993d6514448   \n",
       "3  20250402  1080209bc15196a8bfdc48f002c65787   \n",
       "4  20250402  564c71ba76346d2686c2124b088d0271   \n",
       "\n",
       "                         AGEN_CUSNO    TR_AMT  \\\n",
       "0  7418a3de35c7fb917d8f2a7bc1815b11  11931.82   \n",
       "1  0f1b9608fca0d25607a9bf2647b8392a   5842.47   \n",
       "2  0f1b9608fca0d25607a9bf2647b8392a   9074.06   \n",
       "3  7418a3de35c7fb917d8f2a7bc1815b11  12237.57   \n",
       "4  5f0e8c23f558e1470985cda991b8a796  13824.00   \n",
       "\n",
       "                            PROV_CD                       UNIT_TYP_CD  \n",
       "0  d9d4f495e875a2e075a1a4a6e1b9770f  1d7c2aae840867027b7edd17b6aaa0e9  \n",
       "1  d9d4f495e875a2e075a1a4a6e1b9770f  5f0ad4db43d8723d18169b2e4817a160  \n",
       "2  d9d4f495e875a2e075a1a4a6e1b9770f  5f0ad4db43d8723d18169b2e4817a160  \n",
       "3  d9d4f495e875a2e075a1a4a6e1b9770f  1d7c2aae840867027b7edd17b6aaa0e9  \n",
       "4  d9d4f495e875a2e075a1a4a6e1b9770f  3b777b775721dfa8d36de2a320a03e53  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 加载代发工资数据\n",
    "aget_pay_df = AGET_PAY_data.copy()\n",
    "print(f\"代发工资数据原始形状: {aget_pay_df.shape}\")\n",
    "print(f\"字段: {aget_pay_df.columns.tolist()}\")\n",
    "print(f\"客户数: {aget_pay_df['CUST_NO'].nunique()}\")\n",
    "print(\"\\n数据样例:\")\n",
    "aget_pay_df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c6037cc",
   "metadata": {},
   "source": [
    "### 步骤1: 类别特征编码处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "608eddba",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "开始类别特征编码...\n",
      "单位类型数量: 130\n",
      "省份数量: 5\n",
      "✅ 类别特征编码完成\n"
     ]
    }
   ],
   "source": [
    "# 类别特征编码（参考第一名方案）\n",
    "print(\"开始类别特征编码...\")\n",
    "\n",
    "# 1. 单位类型编码（按频次降序编码）\n",
    "unit_typ_list = aget_pay_df['UNIT_TYP_CD'].value_counts(ascending=False).index\n",
    "unit_typ_dic = {unit_typ_list[i]: i for i, _ in enumerate(unit_typ_list)}\n",
    "print(f\"单位类型数量: {len(unit_typ_dic)}\")\n",
    "\n",
    "# 2. 省份编码（按频次降序编码）\n",
    "prov_cd_list = aget_pay_df['PROV_CD'].value_counts(ascending=False).index\n",
    "prov_cd_dic = {prov_cd_list[i]: i for i, _ in enumerate(prov_cd_list)}\n",
    "print(f\"省份数量: {len(prov_cd_dic)}\")\n",
    "\n",
    "# 3. 应用编码（使用众数填充缺失值）\n",
    "aget_pay_df['PROV_CD'] = aget_pay_df['PROV_CD'].fillna(prov_cd_list[0])  # 用众数填充\n",
    "aget_pay_df['PROV_CD_encoded'] = aget_pay_df['PROV_CD'].map(prov_cd_dic)\n",
    "\n",
    "aget_pay_df['UNIT_TYP_CD'] = aget_pay_df['UNIT_TYP_CD'].fillna(unit_typ_list[0])  # 用众数填充\n",
    "aget_pay_df['UNIT_TYP_CD_encoded'] = aget_pay_df['UNIT_TYP_CD'].map(unit_typ_dic)\n",
    "\n",
    "# 4. 金额变换（参考第一名方案的金额处理方式）\n",
    "aget_pay_df['TR_AMT_transformed'] = aget_pay_df['TR_AMT'].apply(lambda x: round(pow(x/3.12, 3), 2))\n",
    "\n",
    "print(\"✅ 类别特征编码完成\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "921e4dee",
   "metadata": {},
   "source": [
    "### 步骤2: 基础统计特征"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "71999558",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成基础统计特征...\n",
      "✅ 基础统计特征生成完成，特征数: 20\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>aget_pay_count</th>\n",
       "      <th>aget_pay_unit_count</th>\n",
       "      <th>tr_amt_sum</th>\n",
       "      <th>tr_amt_mean</th>\n",
       "      <th>tr_amt_std</th>\n",
       "      <th>tr_amt_median</th>\n",
       "      <th>tr_amt_max</th>\n",
       "      <th>tr_amt_min</th>\n",
       "      <th>tr_amt_skew</th>\n",
       "      <th>...</th>\n",
       "      <th>tr_amt_transformed_sum</th>\n",
       "      <th>tr_amt_transformed_mean</th>\n",
       "      <th>tr_amt_transformed_std</th>\n",
       "      <th>prov_cd_mean</th>\n",
       "      <th>prov_cd_nunique</th>\n",
       "      <th>unit_typ_cd_mean</th>\n",
       "      <th>unit_typ_cd_nunique</th>\n",
       "      <th>tr_amt_range</th>\n",
       "      <th>tr_amt_cv</th>\n",
       "      <th>avg_amt_per_unit</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>00c02817eb07e2226f7e2057c1df05ca</td>\n",
       "      <td>8</td>\n",
       "      <td>1</td>\n",
       "      <td>2565.00</td>\n",
       "      <td>320.625000</td>\n",
       "      <td>315.995451</td>\n",
       "      <td>125.00</td>\n",
       "      <td>700.00</td>\n",
       "      <td>30.00</td>\n",
       "      <td>0.598210</td>\n",
       "      <td>...</td>\n",
       "      <td>3.408156e+07</td>\n",
       "      <td>4.260196e+06</td>\n",
       "      <td>5.824222e+06</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1</td>\n",
       "      <td>49.0</td>\n",
       "      <td>1</td>\n",
       "      <td>670.00</td>\n",
       "      <td>0.985561</td>\n",
       "      <td>2564.997435</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>01a58f5c4eb4b00f50c7262656da3081</td>\n",
       "      <td>3</td>\n",
       "      <td>1</td>\n",
       "      <td>5964.50</td>\n",
       "      <td>1988.166667</td>\n",
       "      <td>837.590903</td>\n",
       "      <td>2471.75</td>\n",
       "      <td>2471.75</td>\n",
       "      <td>1021.00</td>\n",
       "      <td>-1.732051</td>\n",
       "      <td>...</td>\n",
       "      <td>1.029487e+09</td>\n",
       "      <td>3.431623e+08</td>\n",
       "      <td>2.668383e+08</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1</td>\n",
       "      <td>13.0</td>\n",
       "      <td>1</td>\n",
       "      <td>1450.75</td>\n",
       "      <td>0.421288</td>\n",
       "      <td>5964.494036</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0295cf9860071e2374df941b97e400e9</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>9323.48</td>\n",
       "      <td>4661.740000</td>\n",
       "      <td>317.151533</td>\n",
       "      <td>4661.74</td>\n",
       "      <td>4886.00</td>\n",
       "      <td>4437.48</td>\n",
       "      <td>NaN</td>\n",
       "      <td>...</td>\n",
       "      <td>6.717615e+09</td>\n",
       "      <td>3.358808e+09</td>\n",
       "      <td>6.813265e+08</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1</td>\n",
       "      <td>10.0</td>\n",
       "      <td>1</td>\n",
       "      <td>448.52</td>\n",
       "      <td>0.068033</td>\n",
       "      <td>9323.470677</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>02c195666ebe760ece005149180eda98</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>2940.00</td>\n",
       "      <td>1470.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>NaN</td>\n",
       "      <td>...</td>\n",
       "      <td>2.091791e+08</td>\n",
       "      <td>1.045895e+08</td>\n",
       "      <td>0.000000e+00</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1</td>\n",
       "      <td>3.0</td>\n",
       "      <td>1</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>2939.997060</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>038e5ea4892945f19f92076676a2395a</td>\n",
       "      <td>6</td>\n",
       "      <td>2</td>\n",
       "      <td>27202.81</td>\n",
       "      <td>4533.801667</td>\n",
       "      <td>2435.158146</td>\n",
       "      <td>4052.54</td>\n",
       "      <td>8462.64</td>\n",
       "      <td>2530.00</td>\n",
       "      <td>0.805353</td>\n",
       "      <td>...</td>\n",
       "      <td>3.296564e+10</td>\n",
       "      <td>5.494274e+09</td>\n",
       "      <td>7.523843e+09</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1</td>\n",
       "      <td>6.0</td>\n",
       "      <td>2</td>\n",
       "      <td>5932.64</td>\n",
       "      <td>0.537112</td>\n",
       "      <td>13601.398199</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 21 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  aget_pay_count  aget_pay_unit_count  \\\n",
       "0  00c02817eb07e2226f7e2057c1df05ca               8                    1   \n",
       "1  01a58f5c4eb4b00f50c7262656da3081               3                    1   \n",
       "2  0295cf9860071e2374df941b97e400e9               2                    1   \n",
       "3  02c195666ebe760ece005149180eda98               2                    1   \n",
       "4  038e5ea4892945f19f92076676a2395a               6                    2   \n",
       "\n",
       "   tr_amt_sum  tr_amt_mean   tr_amt_std  tr_amt_median  tr_amt_max  \\\n",
       "0     2565.00   320.625000   315.995451         125.00      700.00   \n",
       "1     5964.50  1988.166667   837.590903        2471.75     2471.75   \n",
       "2     9323.48  4661.740000   317.151533        4661.74     4886.00   \n",
       "3     2940.00  1470.000000     0.000000        1470.00     1470.00   \n",
       "4    27202.81  4533.801667  2435.158146        4052.54     8462.64   \n",
       "\n",
       "   tr_amt_min  tr_amt_skew  ...  tr_amt_transformed_sum  \\\n",
       "0       30.00     0.598210  ...            3.408156e+07   \n",
       "1     1021.00    -1.732051  ...            1.029487e+09   \n",
       "2     4437.48          NaN  ...            6.717615e+09   \n",
       "3     1470.00          NaN  ...            2.091791e+08   \n",
       "4     2530.00     0.805353  ...            3.296564e+10   \n",
       "\n",
       "   tr_amt_transformed_mean  tr_amt_transformed_std  prov_cd_mean  \\\n",
       "0             4.260196e+06            5.824222e+06           0.0   \n",
       "1             3.431623e+08            2.668383e+08           0.0   \n",
       "2             3.358808e+09            6.813265e+08           0.0   \n",
       "3             1.045895e+08            0.000000e+00           0.0   \n",
       "4             5.494274e+09            7.523843e+09           0.0   \n",
       "\n",
       "   prov_cd_nunique  unit_typ_cd_mean  unit_typ_cd_nunique  tr_amt_range  \\\n",
       "0                1              49.0                    1        670.00   \n",
       "1                1              13.0                    1       1450.75   \n",
       "2                1              10.0                    1        448.52   \n",
       "3                1               3.0                    1          0.00   \n",
       "4                1               6.0                    2       5932.64   \n",
       "\n",
       "   tr_amt_cv  avg_amt_per_unit  \n",
       "0   0.985561       2564.997435  \n",
       "1   0.421288       5964.494036  \n",
       "2   0.068033       9323.470677  \n",
       "3   0.000000       2939.997060  \n",
       "4   0.537112      13601.398199  \n",
       "\n",
       "[5 rows x 21 columns]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 基础统计特征（按客户聚合）\n",
    "print(\"生成基础统计特征...\")\n",
    "\n",
    "aget_pay_basic_features = aget_pay_df.groupby('CUST_NO').agg(\n",
    "    # 代发笔数\n",
    "    aget_pay_count=('AGEN_CUSNO', 'count'),\n",
    "    \n",
    "    # 代发单位数\n",
    "    aget_pay_unit_count=('AGEN_CUSNO', 'nunique'),\n",
    "    \n",
    "    # 金额统计\n",
    "    tr_amt_sum=('TR_AMT', 'sum'),\n",
    "    tr_amt_mean=('TR_AMT', 'mean'),\n",
    "    tr_amt_std=('TR_AMT', 'std'),\n",
    "    tr_amt_median=('TR_AMT', 'median'),\n",
    "    tr_amt_max=('TR_AMT', 'max'),\n",
    "    tr_amt_min=('TR_AMT', 'min'),\n",
    "    tr_amt_skew=('TR_AMT', lambda x: x.skew()),\n",
    "    tr_amt_kurt=('TR_AMT', lambda x: x.kurt()),\n",
    "    \n",
    "    # 变换后金额统计\n",
    "    tr_amt_transformed_sum=('TR_AMT_transformed', 'sum'),\n",
    "    tr_amt_transformed_mean=('TR_AMT_transformed', 'mean'),\n",
    "    tr_amt_transformed_std=('TR_AMT_transformed', 'std'),\n",
    "    \n",
    "    # 类别特征统计\n",
    "    prov_cd_mean=('PROV_CD_encoded', 'mean'),\n",
    "    prov_cd_nunique=('PROV_CD', 'nunique'),\n",
    "    unit_typ_cd_mean=('UNIT_TYP_CD_encoded', 'mean'),\n",
    "    unit_typ_cd_nunique=('UNIT_TYP_CD', 'nunique'),\n",
    ").reset_index()\n",
    "\n",
    "# 派生特征\n",
    "aget_pay_basic_features['tr_amt_range'] = aget_pay_basic_features['tr_amt_max'] - aget_pay_basic_features['tr_amt_min']\n",
    "aget_pay_basic_features['tr_amt_cv'] = aget_pay_basic_features['tr_amt_std'] / (aget_pay_basic_features['tr_amt_mean'] + 1e-6)  # 变异系数\n",
    "aget_pay_basic_features['avg_amt_per_unit'] = aget_pay_basic_features['tr_amt_sum'] / (aget_pay_basic_features['aget_pay_unit_count'] + 1e-6)  # 单位平均金额\n",
    "\n",
    "print(f\"✅ 基础统计特征生成完成，特征数: {len(aget_pay_basic_features.columns) - 1}\")\n",
    "aget_pay_basic_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ef74afd",
   "metadata": {},
   "source": [
    "### 步骤3: 时间序列特征（代发间隔与规律性）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "dfa14d15",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成时间序列特征...\n",
      "✅ 时间序列特征生成完成，特征数: 9\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>aget_day_diff_max</th>\n",
       "      <th>aget_day_diff_min</th>\n",
       "      <th>aget_day_diff_mean</th>\n",
       "      <th>aget_day_diff_std</th>\n",
       "      <th>aget_day_diff_median</th>\n",
       "      <th>last_aget_days_ago</th>\n",
       "      <th>first_aget_days_ago</th>\n",
       "      <th>aget_day_diff_cv</th>\n",
       "      <th>aget_day_diff_range</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>00c02817eb07e2226f7e2057c1df05ca</td>\n",
       "      <td>33.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>10.428571</td>\n",
       "      <td>14.638501</td>\n",
       "      <td>3.0</td>\n",
       "      <td>0</td>\n",
       "      <td>66</td>\n",
       "      <td>1.403692</td>\n",
       "      <td>33.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>01a58f5c4eb4b00f50c7262656da3081</td>\n",
       "      <td>13.0</td>\n",
       "      <td>12.0</td>\n",
       "      <td>12.500000</td>\n",
       "      <td>0.707107</td>\n",
       "      <td>12.5</td>\n",
       "      <td>0</td>\n",
       "      <td>13</td>\n",
       "      <td>0.056569</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0295cf9860071e2374df941b97e400e9</td>\n",
       "      <td>30.0</td>\n",
       "      <td>30.0</td>\n",
       "      <td>30.000000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>30.0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>02c195666ebe760ece005149180eda98</td>\n",
       "      <td>35.0</td>\n",
       "      <td>35.0</td>\n",
       "      <td>35.000000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>35.0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>038e5ea4892945f19f92076676a2395a</td>\n",
       "      <td>30.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>17.200000</td>\n",
       "      <td>15.786070</td>\n",
       "      <td>26.0</td>\n",
       "      <td>0</td>\n",
       "      <td>60</td>\n",
       "      <td>0.917795</td>\n",
       "      <td>30.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  aget_day_diff_max  aget_day_diff_min  \\\n",
       "0  00c02817eb07e2226f7e2057c1df05ca               33.0                0.0   \n",
       "1  01a58f5c4eb4b00f50c7262656da3081               13.0               12.0   \n",
       "2  0295cf9860071e2374df941b97e400e9               30.0               30.0   \n",
       "3  02c195666ebe760ece005149180eda98               35.0               35.0   \n",
       "4  038e5ea4892945f19f92076676a2395a               30.0                0.0   \n",
       "\n",
       "   aget_day_diff_mean  aget_day_diff_std  aget_day_diff_median  \\\n",
       "0           10.428571          14.638501                   3.0   \n",
       "1           12.500000           0.707107                  12.5   \n",
       "2           30.000000                NaN                  30.0   \n",
       "3           35.000000                NaN                  35.0   \n",
       "4           17.200000          15.786070                  26.0   \n",
       "\n",
       "   last_aget_days_ago  first_aget_days_ago  aget_day_diff_cv  \\\n",
       "0                   0                   66          1.403692   \n",
       "1                   0                   13          0.056569   \n",
       "2                   0                    0               NaN   \n",
       "3                   0                    0               NaN   \n",
       "4                   0                   60          0.917795   \n",
       "\n",
       "   aget_day_diff_range  \n",
       "0                 33.0  \n",
       "1                  1.0  \n",
       "2                  0.0  \n",
       "3                  0.0  \n",
       "4                 30.0  "
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 时间序列特征（参考第一名方案的代发间隔处理）\n",
    "print(\"生成时间序列特征...\")\n",
    "\n",
    "# 日期转换与排序\n",
    "aget_pay_time = aget_pay_df.copy()\n",
    "aget_pay_time['DATE'] = pd.to_datetime(aget_pay_time['DATE'], format='%Y%m%d')\n",
    "aget_pay_time = aget_pay_time.sort_values(['CUST_NO', 'DATE'])\n",
    "\n",
    "# 计算相邻两次代发的间隔天数\n",
    "aget_pay_time['aget_day_diff'] = aget_pay_time.groupby('CUST_NO')['DATE'].diff().dt.days\n",
    "\n",
    "# 代发间隔统计特征\n",
    "aget_pay_time_features = aget_pay_time.dropna(subset=['aget_day_diff']).groupby('CUST_NO').agg(\n",
    "    # 间隔天数统计\n",
    "    aget_day_diff_max=('aget_day_diff', 'max'),\n",
    "    aget_day_diff_min=('aget_day_diff', 'min'),\n",
    "    aget_day_diff_mean=('aget_day_diff', 'mean'),\n",
    "    aget_day_diff_std=('aget_day_diff', 'std'),\n",
    "    aget_day_diff_median=('aget_day_diff', 'median'),\n",
    "    \n",
    "    # 最近一次代发距今天数\n",
    "    last_aget_days_ago=('DATE', lambda x: (x.max() - x.iloc[-1]).days),\n",
    "    \n",
    "    # 首次代发距今天数\n",
    "    first_aget_days_ago=('DATE', lambda x: (x.max() - x.iloc[0]).days),\n",
    ").reset_index()\n",
    "\n",
    "# 派生特征\n",
    "aget_pay_time_features['aget_day_diff_cv'] = aget_pay_time_features['aget_day_diff_std'] / (aget_pay_time_features['aget_day_diff_mean'] + 1e-6)  # 间隔变异系数(规律性)\n",
    "aget_pay_time_features['aget_day_diff_range'] = aget_pay_time_features['aget_day_diff_max'] - aget_pay_time_features['aget_day_diff_min']\n",
    "\n",
    "print(f\"✅ 时间序列特征生成完成，特征数: {len(aget_pay_time_features.columns) - 1}\")\n",
    "aget_pay_time_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33da16b8",
   "metadata": {},
   "source": [
    "### 步骤4: 单位维度特征（单位集中度与稳定性）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "a0de479e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成单位维度特征...\n",
      "✅ 单位维度特征生成完成，特征数: 4\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>main_unit_ratio</th>\n",
       "      <th>main_unit_type_ratio</th>\n",
       "      <th>main_prov_ratio</th>\n",
       "      <th>max_unit_amt_ratio</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>00c02817eb07e2226f7e2057c1df05ca</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>01a58f5c4eb4b00f50c7262656da3081</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0295cf9860071e2374df941b97e400e9</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>02c195666ebe760ece005149180eda98</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>038e5ea4892945f19f92076676a2395a</td>\n",
       "      <td>0.5</td>\n",
       "      <td>0.5</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.720985</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  main_unit_ratio  main_unit_type_ratio  \\\n",
       "0  00c02817eb07e2226f7e2057c1df05ca              1.0                   1.0   \n",
       "1  01a58f5c4eb4b00f50c7262656da3081              1.0                   1.0   \n",
       "2  0295cf9860071e2374df941b97e400e9              1.0                   1.0   \n",
       "3  02c195666ebe760ece005149180eda98              1.0                   1.0   \n",
       "4  038e5ea4892945f19f92076676a2395a              0.5                   0.5   \n",
       "\n",
       "   main_prov_ratio  max_unit_amt_ratio  \n",
       "0              1.0            1.000000  \n",
       "1              1.0            1.000000  \n",
       "2              1.0            1.000000  \n",
       "3              1.0            1.000000  \n",
       "4              1.0            0.720985  "
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 单位维度特征\n",
    "print(\"生成单位维度特征...\")\n",
    "\n",
    "# 单位集中度（主要单位占比）\n",
    "def unit_concentration(x):\n",
    "    \"\"\"计算主要单位的代发次数占比\"\"\"\n",
    "    if len(x) == 0:\n",
    "        return 0\n",
    "    return x.value_counts().iloc[0] / len(x)\n",
    "\n",
    "aget_pay_unit_features = aget_pay_df.groupby('CUST_NO').agg(\n",
    "    # 主要单位占比\n",
    "    main_unit_ratio=('AGEN_CUSNO', unit_concentration),\n",
    "    \n",
    "    # 主要单位类型占比\n",
    "    main_unit_type_ratio=('UNIT_TYP_CD', unit_concentration),\n",
    "    \n",
    "    # 主要省份占比\n",
    "    main_prov_ratio=('PROV_CD', unit_concentration),\n",
    ").reset_index()\n",
    "\n",
    "# 单位金额统计\n",
    "unit_amt_stats = aget_pay_df.groupby(['CUST_NO', 'AGEN_CUSNO'])['TR_AMT'].agg(['sum', 'mean', 'count']).reset_index()\n",
    "\n",
    "# 最大代发单位的金额占比\n",
    "max_unit_amt = unit_amt_stats.groupby('CUST_NO').apply(\n",
    "    lambda x: x.loc[x['sum'].idxmax(), 'sum'] / x['sum'].sum() if len(x) > 0 else 0\n",
    ").reset_index()\n",
    "max_unit_amt.columns = ['CUST_NO', 'max_unit_amt_ratio']\n",
    "\n",
    "aget_pay_unit_features = aget_pay_unit_features.merge(max_unit_amt, on='CUST_NO', how='left')\n",
    "\n",
    "print(f\"✅ 单位维度特征生成完成，特征数: {len(aget_pay_unit_features.columns) - 1}\")\n",
    "aget_pay_unit_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f51fab97",
   "metadata": {},
   "source": [
    "### 步骤5: 月度特征（分月统计）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "368fc70d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成月度特征...\n",
      "✅ 月度特征生成完成，特征数: 13\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>active_months</th>\n",
       "      <th>avg_month_count</th>\n",
       "      <th>month_count_std</th>\n",
       "      <th>month_count_max</th>\n",
       "      <th>month_count_min</th>\n",
       "      <th>avg_month_amt</th>\n",
       "      <th>month_amt_std</th>\n",
       "      <th>month_amt_max</th>\n",
       "      <th>month_amt_min</th>\n",
       "      <th>avg_month_unit</th>\n",
       "      <th>month_unit_std</th>\n",
       "      <th>month_count_cv</th>\n",
       "      <th>month_amt_cv</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>00c02817eb07e2226f7e2057c1df05ca</td>\n",
       "      <td>3</td>\n",
       "      <td>2.666667</td>\n",
       "      <td>0.577350</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>855.000000</td>\n",
       "      <td>30.000000</td>\n",
       "      <td>885.00</td>\n",
       "      <td>825.00</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.216506</td>\n",
       "      <td>0.035088</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>01a58f5c4eb4b00f50c7262656da3081</td>\n",
       "      <td>2</td>\n",
       "      <td>1.500000</td>\n",
       "      <td>0.707107</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>2982.250000</td>\n",
       "      <td>721.956024</td>\n",
       "      <td>3492.75</td>\n",
       "      <td>2471.75</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.471404</td>\n",
       "      <td>0.242084</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0295cf9860071e2374df941b97e400e9</td>\n",
       "      <td>2</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>4661.740000</td>\n",
       "      <td>317.151533</td>\n",
       "      <td>4886.00</td>\n",
       "      <td>4437.48</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.068033</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>02c195666ebe760ece005149180eda98</td>\n",
       "      <td>2</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>1470.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>038e5ea4892945f19f92076676a2395a</td>\n",
       "      <td>3</td>\n",
       "      <td>2.000000</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>3</td>\n",
       "      <td>1</td>\n",
       "      <td>9067.603333</td>\n",
       "      <td>7068.183617</td>\n",
       "      <td>16567.72</td>\n",
       "      <td>2530.00</td>\n",
       "      <td>1.666667</td>\n",
       "      <td>0.57735</td>\n",
       "      <td>0.500000</td>\n",
       "      <td>0.779499</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  active_months  avg_month_count  \\\n",
       "0  00c02817eb07e2226f7e2057c1df05ca              3         2.666667   \n",
       "1  01a58f5c4eb4b00f50c7262656da3081              2         1.500000   \n",
       "2  0295cf9860071e2374df941b97e400e9              2         1.000000   \n",
       "3  02c195666ebe760ece005149180eda98              2         1.000000   \n",
       "4  038e5ea4892945f19f92076676a2395a              3         2.000000   \n",
       "\n",
       "   month_count_std  month_count_max  month_count_min  avg_month_amt  \\\n",
       "0         0.577350                3                2     855.000000   \n",
       "1         0.707107                2                1    2982.250000   \n",
       "2         0.000000                1                1    4661.740000   \n",
       "3         0.000000                1                1    1470.000000   \n",
       "4         1.000000                3                1    9067.603333   \n",
       "\n",
       "   month_amt_std  month_amt_max  month_amt_min  avg_month_unit  \\\n",
       "0      30.000000         885.00         825.00        1.000000   \n",
       "1     721.956024        3492.75        2471.75        1.000000   \n",
       "2     317.151533        4886.00        4437.48        1.000000   \n",
       "3       0.000000        1470.00        1470.00        1.000000   \n",
       "4    7068.183617       16567.72        2530.00        1.666667   \n",
       "\n",
       "   month_unit_std  month_count_cv  month_amt_cv  \n",
       "0         0.00000        0.216506      0.035088  \n",
       "1         0.00000        0.471404      0.242084  \n",
       "2         0.00000        0.000000      0.068033  \n",
       "3         0.00000        0.000000      0.000000  \n",
       "4         0.57735        0.500000      0.779499  "
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 月度特征\n",
    "print(\"生成月度特征...\")\n",
    "\n",
    "# 提取月份\n",
    "aget_pay_month = aget_pay_df.copy()\n",
    "aget_pay_month['month'] = pd.to_datetime(aget_pay_month['DATE'], format='%Y%m%d').dt.month\n",
    "\n",
    "# 按客户和月份聚合\n",
    "aget_pay_month_agg = aget_pay_month.groupby(['CUST_NO', 'month']).agg(\n",
    "    month_count=('AGEN_CUSNO', 'count'),\n",
    "    month_amt_sum=('TR_AMT', 'sum'),\n",
    "    month_amt_mean=('TR_AMT', 'mean'),\n",
    "    month_unit_count=('AGEN_CUSNO', 'nunique'),\n",
    ").reset_index()\n",
    "\n",
    "# 月度趋势特征\n",
    "aget_pay_month_features = aget_pay_month_agg.groupby('CUST_NO').agg(\n",
    "    # 活跃月份数\n",
    "    active_months=('month', 'nunique'),\n",
    "    \n",
    "    # 月均代发次数\n",
    "    avg_month_count=('month_count', 'mean'),\n",
    "    month_count_std=('month_count', 'std'),\n",
    "    month_count_max=('month_count', 'max'),\n",
    "    month_count_min=('month_count', 'min'),\n",
    "    \n",
    "    # 月均代发金额\n",
    "    avg_month_amt=('month_amt_sum', 'mean'),\n",
    "    month_amt_std=('month_amt_sum', 'std'),\n",
    "    month_amt_max=('month_amt_sum', 'max'),\n",
    "    month_amt_min=('month_amt_sum', 'min'),\n",
    "    \n",
    "    # 月均单位数\n",
    "    avg_month_unit=('month_unit_count', 'mean'),\n",
    "    month_unit_std=('month_unit_count', 'std'),\n",
    ").reset_index()\n",
    "\n",
    "# 月度稳定性（变异系数）\n",
    "aget_pay_month_features['month_count_cv'] = aget_pay_month_features['month_count_std'] / (aget_pay_month_features['avg_month_count'] + 1e-6)\n",
    "aget_pay_month_features['month_amt_cv'] = aget_pay_month_features['month_amt_std'] / (aget_pay_month_features['avg_month_amt'] + 1e-6)\n",
    "\n",
    "print(f\"✅ 月度特征生成完成，特征数: {len(aget_pay_month_features.columns) - 1}\")\n",
    "aget_pay_month_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "079e0ac4",
   "metadata": {},
   "source": [
    "### 步骤6: 合并代发工资所有特征并保存"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "696a4593",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "合并所有代发工资特征...\n",
      "\n",
      "============================================================\n",
      "📊 代发工资特征工程总结\n",
      "============================================================\n",
      "✅ 特征总数: 46\n",
      "✅ 客户数: 530\n",
      "✅ 特征列表:\n",
      "   1. aget_pay_count\n",
      "   2. aget_pay_unit_count\n",
      "   3. tr_amt_sum\n",
      "   4. tr_amt_mean\n",
      "   5. tr_amt_std\n",
      "   6. tr_amt_median\n",
      "   7. tr_amt_max\n",
      "   8. tr_amt_min\n",
      "   9. tr_amt_skew\n",
      "   10. tr_amt_kurt\n",
      "   11. tr_amt_transformed_sum\n",
      "   12. tr_amt_transformed_mean\n",
      "   13. tr_amt_transformed_std\n",
      "   14. prov_cd_mean\n",
      "   15. prov_cd_nunique\n",
      "   16. unit_typ_cd_mean\n",
      "   17. unit_typ_cd_nunique\n",
      "   18. tr_amt_range\n",
      "   19. tr_amt_cv\n",
      "   20. avg_amt_per_unit\n",
      "   21. aget_day_diff_max\n",
      "   22. aget_day_diff_min\n",
      "   23. aget_day_diff_mean\n",
      "   24. aget_day_diff_std\n",
      "   25. aget_day_diff_median\n",
      "   26. last_aget_days_ago\n",
      "   27. first_aget_days_ago\n",
      "   28. aget_day_diff_cv\n",
      "   29. aget_day_diff_range\n",
      "   30. main_unit_ratio\n",
      "   31. main_unit_type_ratio\n",
      "   32. main_prov_ratio\n",
      "   33. max_unit_amt_ratio\n",
      "   34. active_months\n",
      "   35. avg_month_count\n",
      "   36. month_count_std\n",
      "   37. month_count_max\n",
      "   38. month_count_min\n",
      "   39. avg_month_amt\n",
      "   40. month_amt_std\n",
      "   41. month_amt_max\n",
      "   42. month_amt_min\n",
      "   43. avg_month_unit\n",
      "   44. month_unit_std\n",
      "   45. month_count_cv\n",
      "   46. month_amt_cv\n",
      "\n",
      "✅ 代发工资特征已保存至: ./feature/AGET_PAY_features.pkl\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>aget_pay_count</th>\n",
       "      <th>aget_pay_unit_count</th>\n",
       "      <th>tr_amt_sum</th>\n",
       "      <th>tr_amt_mean</th>\n",
       "      <th>tr_amt_std</th>\n",
       "      <th>tr_amt_median</th>\n",
       "      <th>tr_amt_max</th>\n",
       "      <th>tr_amt_min</th>\n",
       "      <th>tr_amt_skew</th>\n",
       "      <th>...</th>\n",
       "      <th>month_count_max</th>\n",
       "      <th>month_count_min</th>\n",
       "      <th>avg_month_amt</th>\n",
       "      <th>month_amt_std</th>\n",
       "      <th>month_amt_max</th>\n",
       "      <th>month_amt_min</th>\n",
       "      <th>avg_month_unit</th>\n",
       "      <th>month_unit_std</th>\n",
       "      <th>month_count_cv</th>\n",
       "      <th>month_amt_cv</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>00c02817eb07e2226f7e2057c1df05ca</td>\n",
       "      <td>8</td>\n",
       "      <td>1</td>\n",
       "      <td>2565.00</td>\n",
       "      <td>320.625000</td>\n",
       "      <td>315.995451</td>\n",
       "      <td>125.00</td>\n",
       "      <td>700.00</td>\n",
       "      <td>30.00</td>\n",
       "      <td>0.598210</td>\n",
       "      <td>...</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>855.000000</td>\n",
       "      <td>30.000000</td>\n",
       "      <td>885.00</td>\n",
       "      <td>825.00</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.216506</td>\n",
       "      <td>0.035088</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>01a58f5c4eb4b00f50c7262656da3081</td>\n",
       "      <td>3</td>\n",
       "      <td>1</td>\n",
       "      <td>5964.50</td>\n",
       "      <td>1988.166667</td>\n",
       "      <td>837.590903</td>\n",
       "      <td>2471.75</td>\n",
       "      <td>2471.75</td>\n",
       "      <td>1021.00</td>\n",
       "      <td>-1.732051</td>\n",
       "      <td>...</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>2982.250000</td>\n",
       "      <td>721.956024</td>\n",
       "      <td>3492.75</td>\n",
       "      <td>2471.75</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.471404</td>\n",
       "      <td>0.242084</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0295cf9860071e2374df941b97e400e9</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>9323.48</td>\n",
       "      <td>4661.740000</td>\n",
       "      <td>317.151533</td>\n",
       "      <td>4661.74</td>\n",
       "      <td>4886.00</td>\n",
       "      <td>4437.48</td>\n",
       "      <td>NaN</td>\n",
       "      <td>...</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>4661.740000</td>\n",
       "      <td>317.151533</td>\n",
       "      <td>4886.00</td>\n",
       "      <td>4437.48</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.068033</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>02c195666ebe760ece005149180eda98</td>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>2940.00</td>\n",
       "      <td>1470.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>NaN</td>\n",
       "      <td>...</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>1470.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>1470.00</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>038e5ea4892945f19f92076676a2395a</td>\n",
       "      <td>6</td>\n",
       "      <td>2</td>\n",
       "      <td>27202.81</td>\n",
       "      <td>4533.801667</td>\n",
       "      <td>2435.158146</td>\n",
       "      <td>4052.54</td>\n",
       "      <td>8462.64</td>\n",
       "      <td>2530.00</td>\n",
       "      <td>0.805353</td>\n",
       "      <td>...</td>\n",
       "      <td>3</td>\n",
       "      <td>1</td>\n",
       "      <td>9067.603333</td>\n",
       "      <td>7068.183617</td>\n",
       "      <td>16567.72</td>\n",
       "      <td>2530.00</td>\n",
       "      <td>1.666667</td>\n",
       "      <td>0.57735</td>\n",
       "      <td>0.500000</td>\n",
       "      <td>0.779499</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 47 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  aget_pay_count  aget_pay_unit_count  \\\n",
       "0  00c02817eb07e2226f7e2057c1df05ca               8                    1   \n",
       "1  01a58f5c4eb4b00f50c7262656da3081               3                    1   \n",
       "2  0295cf9860071e2374df941b97e400e9               2                    1   \n",
       "3  02c195666ebe760ece005149180eda98               2                    1   \n",
       "4  038e5ea4892945f19f92076676a2395a               6                    2   \n",
       "\n",
       "   tr_amt_sum  tr_amt_mean   tr_amt_std  tr_amt_median  tr_amt_max  \\\n",
       "0     2565.00   320.625000   315.995451         125.00      700.00   \n",
       "1     5964.50  1988.166667   837.590903        2471.75     2471.75   \n",
       "2     9323.48  4661.740000   317.151533        4661.74     4886.00   \n",
       "3     2940.00  1470.000000     0.000000        1470.00     1470.00   \n",
       "4    27202.81  4533.801667  2435.158146        4052.54     8462.64   \n",
       "\n",
       "   tr_amt_min  tr_amt_skew  ...  month_count_max  month_count_min  \\\n",
       "0       30.00     0.598210  ...                3                2   \n",
       "1     1021.00    -1.732051  ...                2                1   \n",
       "2     4437.48          NaN  ...                1                1   \n",
       "3     1470.00          NaN  ...                1                1   \n",
       "4     2530.00     0.805353  ...                3                1   \n",
       "\n",
       "   avg_month_amt  month_amt_std  month_amt_max  month_amt_min  avg_month_unit  \\\n",
       "0     855.000000      30.000000         885.00         825.00        1.000000   \n",
       "1    2982.250000     721.956024        3492.75        2471.75        1.000000   \n",
       "2    4661.740000     317.151533        4886.00        4437.48        1.000000   \n",
       "3    1470.000000       0.000000        1470.00        1470.00        1.000000   \n",
       "4    9067.603333    7068.183617       16567.72        2530.00        1.666667   \n",
       "\n",
       "   month_unit_std  month_count_cv  month_amt_cv  \n",
       "0         0.00000        0.216506      0.035088  \n",
       "1         0.00000        0.471404      0.242084  \n",
       "2         0.00000        0.000000      0.068033  \n",
       "3         0.00000        0.000000      0.000000  \n",
       "4         0.57735        0.500000      0.779499  \n",
       "\n",
       "[5 rows x 47 columns]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 合并所有代发工资特征\n",
    "print(\"合并所有代发工资特征...\")\n",
    "\n",
    "# 逐步合并\n",
    "aget_pay_features_final = aget_pay_basic_features.copy()\n",
    "aget_pay_features_final = aget_pay_features_final.merge(aget_pay_time_features, on='CUST_NO', how='left')\n",
    "aget_pay_features_final = aget_pay_features_final.merge(aget_pay_unit_features, on='CUST_NO', how='left')\n",
    "aget_pay_features_final = aget_pay_features_final.merge(aget_pay_month_features, on='CUST_NO', how='left')\n",
    "\n",
    "print(f\"\\n{'='*60}\")\n",
    "print(f\"📊 代发工资特征工程总结\")\n",
    "print(f\"{'='*60}\")\n",
    "print(f\"✅ 特征总数: {len(aget_pay_features_final.columns) - 1}\")\n",
    "print(f\"✅ 客户数: {len(aget_pay_features_final)}\")\n",
    "print(f\"✅ 特征列表:\")\n",
    "for i, col in enumerate(aget_pay_features_final.columns):\n",
    "    if col != 'CUST_NO':\n",
    "        print(f\"   {i}. {col}\")\n",
    "\n",
    "# 保存特征到pickle文件\n",
    "feature_path = './feature/AGET_PAY_features.pkl'\n",
    "os.makedirs('./feature', exist_ok=True)\n",
    "with open(feature_path, 'wb') as f:\n",
    "    pickle.dump(aget_pay_features_final, f)\n",
    "\n",
    "print(f\"\\n✅ 代发工资特征已保存至: {feature_path}\")\n",
    "aget_pay_features_final.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5335fe7c",
   "metadata": {},
   "source": [
    "## 💳 信用卡交易流水表(CCD_TR_DTL)特征工程\n",
    "\n",
    "### 策略说明\n",
    "基于2024年营销响应第一名方案的特征设计：\n",
    "1. 基础消费统计（笔数、金额、频率）\n",
    "2. 时间序列特征（最近性、间隔、活跃度）\n",
    "3. 消费行为特征（金额分布、大小额占比）\n",
    "4. 商户维度特征（多样性、集中度、忠诚度）\n",
    "5. 时间模式特征（工作日/周末、月度趋势）\n",
    "6. 风险相关特征（异常交易、突变检测）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "b6ef0fcb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "信用卡交易数据原始形状: (129, 5)\n",
      "字段: ['DATE_TR', 'CUST_NO', 'COD_PSG', 'COD_TR', 'AMT_TR']\n",
      "客户数: 18\n",
      "\n",
      "数据样例:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATE_TR</th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>COD_PSG</th>\n",
       "      <th>COD_TR</th>\n",
       "      <th>AMT_TR</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20250601</td>\n",
       "      <td>756ac190fc2116b999f8b39059cf86f2</td>\n",
       "      <td>d41d8cd98f00b204e9800998ecf8427e</td>\n",
       "      <td>05b9272c981cd6348ac8c16d825ccb9a</td>\n",
       "      <td>416.67</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20250601</td>\n",
       "      <td>7452817bcd0b031bb32eecc244f77886</td>\n",
       "      <td>d41d8cd98f00b204e9800998ecf8427e</td>\n",
       "      <td>05b9272c981cd6348ac8c16d825ccb9a</td>\n",
       "      <td>498.33</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20250601</td>\n",
       "      <td>fcf175e319f5b83b448845050aabafaa</td>\n",
       "      <td>d41d8cd98f00b204e9800998ecf8427e</td>\n",
       "      <td>05b9272c981cd6348ac8c16d825ccb9a</td>\n",
       "      <td>416.67</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20250601</td>\n",
       "      <td>f0bcb629f35be0fd119860a14a266fb6</td>\n",
       "      <td>d41d8cd98f00b204e9800998ecf8427e</td>\n",
       "      <td>05b9272c981cd6348ac8c16d825ccb9a</td>\n",
       "      <td>1234.58</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20250601</td>\n",
       "      <td>2748b8bdd1889567b1755668e79b5ee7</td>\n",
       "      <td>d41d8cd98f00b204e9800998ecf8427e</td>\n",
       "      <td>05b9272c981cd6348ac8c16d825ccb9a</td>\n",
       "      <td>330.00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    DATE_TR                           CUST_NO  \\\n",
       "0  20250601  756ac190fc2116b999f8b39059cf86f2   \n",
       "1  20250601  7452817bcd0b031bb32eecc244f77886   \n",
       "2  20250601  fcf175e319f5b83b448845050aabafaa   \n",
       "3  20250601  f0bcb629f35be0fd119860a14a266fb6   \n",
       "4  20250601  2748b8bdd1889567b1755668e79b5ee7   \n",
       "\n",
       "                            COD_PSG                            COD_TR   AMT_TR  \n",
       "0  d41d8cd98f00b204e9800998ecf8427e  05b9272c981cd6348ac8c16d825ccb9a   416.67  \n",
       "1  d41d8cd98f00b204e9800998ecf8427e  05b9272c981cd6348ac8c16d825ccb9a   498.33  \n",
       "2  d41d8cd98f00b204e9800998ecf8427e  05b9272c981cd6348ac8c16d825ccb9a   416.67  \n",
       "3  d41d8cd98f00b204e9800998ecf8427e  05b9272c981cd6348ac8c16d825ccb9a  1234.58  \n",
       "4  d41d8cd98f00b204e9800998ecf8427e  05b9272c981cd6348ac8c16d825ccb9a   330.00  "
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 加载信用卡交易数据\n",
    "ccd_tr_dtl = CCD_TR_DTL_data.copy()\n",
    "print(f\"信用卡交易数据原始形状: {ccd_tr_dtl.shape}\")\n",
    "print(f\"字段: {ccd_tr_dtl.columns.tolist()}\")\n",
    "print(f\"客户数: {ccd_tr_dtl['CUST_NO'].nunique()}\")\n",
    "print(\"\\n数据样例:\")\n",
    "ccd_tr_dtl.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cfbda378",
   "metadata": {},
   "source": [
    "### 步骤1: 日期转换与时间特征"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "7f889dbc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "开始日期转换...\n",
      "使用日期列: DATE_TR\n",
      "✅ 日期转换完成:\n",
      "   交易日期范围: 2025-04-01 ~ 2025-06-27\n",
      "   距今天数范围: 0 ~ 87\n",
      "   月份分布:\n",
      "date_months_to_now\n",
      "0    56\n",
      "1    38\n",
      "2    35\n",
      "Name: count, dtype: int64\n",
      "✅ 日期转换完成，数据形状: (129, 14)\n"
     ]
    }
   ],
   "source": [
    "# 日期转换与时间特征\n",
    "print(\"开始日期转换...\")\n",
    "\n",
    "# 根据实际数据字段调整：DATE_TR是日期列\n",
    "date_col = 'DATE_TR'\n",
    "print(f\"使用日期列: {date_col}\")\n",
    "\n",
    "ccd_tr_dtl = get_days_to_now(ccd_tr_dtl, date_col)\n",
    "\n",
    "# 额外的时间特征\n",
    "ccd_tr_dtl['year'] = pd.to_datetime(ccd_tr_dtl[date_col], format='%Y%m%d').dt.year\n",
    "ccd_tr_dtl['month'] = pd.to_datetime(ccd_tr_dtl[date_col], format='%Y%m%d').dt.month\n",
    "ccd_tr_dtl['day'] = pd.to_datetime(ccd_tr_dtl[date_col], format='%Y%m%d').dt.day\n",
    "ccd_tr_dtl['dayofweek'] = pd.to_datetime(ccd_tr_dtl[date_col], format='%Y%m%d').dt.dayofweek  # 0=周一, 6=周日\n",
    "ccd_tr_dtl['is_weekend'] = ccd_tr_dtl['dayofweek'].apply(lambda x: 1 if x >= 5 else 0)  # 周末标识\n",
    "\n",
    "print(f\"✅ 日期转换完成，数据形状: {ccd_tr_dtl.shape}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "568eecc1",
   "metadata": {},
   "source": [
    "### 步骤2: 基础消费统计特征"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "bf9dc22b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成基础消费统计特征...\n",
      "使用金额列: AMT_TR\n",
      "✅ 基础消费统计特征生成完成，特征数: 19\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>ccd_tr_count</th>\n",
       "      <th>ccd_amt_sum</th>\n",
       "      <th>ccd_amt_mean</th>\n",
       "      <th>ccd_amt_std</th>\n",
       "      <th>ccd_amt_median</th>\n",
       "      <th>ccd_amt_max</th>\n",
       "      <th>ccd_amt_min</th>\n",
       "      <th>ccd_amt_skew</th>\n",
       "      <th>ccd_amt_kurt</th>\n",
       "      <th>ccd_amt_q25</th>\n",
       "      <th>ccd_amt_q75</th>\n",
       "      <th>active_days</th>\n",
       "      <th>weekday_count</th>\n",
       "      <th>weekend_count</th>\n",
       "      <th>ccd_amt_range</th>\n",
       "      <th>ccd_amt_cv</th>\n",
       "      <th>ccd_amt_per_trans</th>\n",
       "      <th>ccd_trans_per_day</th>\n",
       "      <th>weekend_ratio</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>064898a7d7d2bc34872e423170f97bfb</td>\n",
       "      <td>12</td>\n",
       "      <td>27996.96</td>\n",
       "      <td>2333.080000</td>\n",
       "      <td>1055.138950</td>\n",
       "      <td>2916.33</td>\n",
       "      <td>2916.33</td>\n",
       "      <td>583.33</td>\n",
       "      <td>-1.326650</td>\n",
       "      <td>-0.325926</td>\n",
       "      <td>2333.08</td>\n",
       "      <td>2916.33</td>\n",
       "      <td>6</td>\n",
       "      <td>5</td>\n",
       "      <td>7</td>\n",
       "      <td>2333.00</td>\n",
       "      <td>0.452252</td>\n",
       "      <td>2333.079806</td>\n",
       "      <td>2.000000</td>\n",
       "      <td>0.583333</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0b3a60d7823eacb570f33592bca838ad</td>\n",
       "      <td>9</td>\n",
       "      <td>3004.00</td>\n",
       "      <td>333.777778</td>\n",
       "      <td>999.833333</td>\n",
       "      <td>0.50</td>\n",
       "      <td>3000.00</td>\n",
       "      <td>0.50</td>\n",
       "      <td>3.000000</td>\n",
       "      <td>9.000000</td>\n",
       "      <td>0.50</td>\n",
       "      <td>0.50</td>\n",
       "      <td>5</td>\n",
       "      <td>9</td>\n",
       "      <td>0</td>\n",
       "      <td>2999.50</td>\n",
       "      <td>2.995506</td>\n",
       "      <td>333.777741</td>\n",
       "      <td>1.800000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2748b8bdd1889567b1755668e79b5ee7</td>\n",
       "      <td>12</td>\n",
       "      <td>41472.00</td>\n",
       "      <td>3456.000000</td>\n",
       "      <td>1885.049360</td>\n",
       "      <td>4497.00</td>\n",
       "      <td>4500.00</td>\n",
       "      <td>330.00</td>\n",
       "      <td>-1.326648</td>\n",
       "      <td>-0.325928</td>\n",
       "      <td>3455.25</td>\n",
       "      <td>4497.75</td>\n",
       "      <td>9</td>\n",
       "      <td>10</td>\n",
       "      <td>2</td>\n",
       "      <td>4170.00</td>\n",
       "      <td>0.545443</td>\n",
       "      <td>3455.999712</td>\n",
       "      <td>1.333333</td>\n",
       "      <td>0.166667</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>42c6b970935d3833c31276fe60aba663</td>\n",
       "      <td>5</td>\n",
       "      <td>85332.32</td>\n",
       "      <td>17066.464000</td>\n",
       "      <td>35185.650870</td>\n",
       "      <td>1666.33</td>\n",
       "      <td>80000.00</td>\n",
       "      <td>333.33</td>\n",
       "      <td>2.234555</td>\n",
       "      <td>4.994665</td>\n",
       "      <td>1666.33</td>\n",
       "      <td>1666.33</td>\n",
       "      <td>3</td>\n",
       "      <td>1</td>\n",
       "      <td>4</td>\n",
       "      <td>79666.67</td>\n",
       "      <td>2.061684</td>\n",
       "      <td>17066.460587</td>\n",
       "      <td>1.666666</td>\n",
       "      <td>0.800000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4544d13ddc128e03327fe7ffac914765</td>\n",
       "      <td>10</td>\n",
       "      <td>130.00</td>\n",
       "      <td>13.000000</td>\n",
       "      <td>9.055385</td>\n",
       "      <td>20.00</td>\n",
       "      <td>20.00</td>\n",
       "      <td>1.00</td>\n",
       "      <td>-0.499411</td>\n",
       "      <td>-2.224845</td>\n",
       "      <td>3.00</td>\n",
       "      <td>20.00</td>\n",
       "      <td>7</td>\n",
       "      <td>9</td>\n",
       "      <td>1</td>\n",
       "      <td>19.00</td>\n",
       "      <td>0.696568</td>\n",
       "      <td>12.999999</td>\n",
       "      <td>1.428571</td>\n",
       "      <td>0.100000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  ccd_tr_count  ccd_amt_sum  ccd_amt_mean  \\\n",
       "0  064898a7d7d2bc34872e423170f97bfb            12     27996.96   2333.080000   \n",
       "1  0b3a60d7823eacb570f33592bca838ad             9      3004.00    333.777778   \n",
       "2  2748b8bdd1889567b1755668e79b5ee7            12     41472.00   3456.000000   \n",
       "3  42c6b970935d3833c31276fe60aba663             5     85332.32  17066.464000   \n",
       "4  4544d13ddc128e03327fe7ffac914765            10       130.00     13.000000   \n",
       "\n",
       "    ccd_amt_std  ccd_amt_median  ccd_amt_max  ccd_amt_min  ccd_amt_skew  \\\n",
       "0   1055.138950         2916.33      2916.33       583.33     -1.326650   \n",
       "1    999.833333            0.50      3000.00         0.50      3.000000   \n",
       "2   1885.049360         4497.00      4500.00       330.00     -1.326648   \n",
       "3  35185.650870         1666.33     80000.00       333.33      2.234555   \n",
       "4      9.055385           20.00        20.00         1.00     -0.499411   \n",
       "\n",
       "   ccd_amt_kurt  ccd_amt_q25  ccd_amt_q75  active_days  weekday_count  \\\n",
       "0     -0.325926      2333.08      2916.33            6              5   \n",
       "1      9.000000         0.50         0.50            5              9   \n",
       "2     -0.325928      3455.25      4497.75            9             10   \n",
       "3      4.994665      1666.33      1666.33            3              1   \n",
       "4     -2.224845         3.00        20.00            7              9   \n",
       "\n",
       "   weekend_count  ccd_amt_range  ccd_amt_cv  ccd_amt_per_trans  \\\n",
       "0              7        2333.00    0.452252        2333.079806   \n",
       "1              0        2999.50    2.995506         333.777741   \n",
       "2              2        4170.00    0.545443        3455.999712   \n",
       "3              4       79666.67    2.061684       17066.460587   \n",
       "4              1          19.00    0.696568          12.999999   \n",
       "\n",
       "   ccd_trans_per_day  weekend_ratio  \n",
       "0           2.000000       0.583333  \n",
       "1           1.800000       0.000000  \n",
       "2           1.333333       0.166667  \n",
       "3           1.666666       0.800000  \n",
       "4           1.428571       0.100000  "
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 基础消费统计特征\n",
    "print(\"生成基础消费统计特征...\")\n",
    "\n",
    "# 金额列是AMT_TR\n",
    "amt_col = 'AMT_TR'\n",
    "print(f\"使用金额列: {amt_col}\")\n",
    "\n",
    "ccd_basic_features = ccd_tr_dtl.groupby('CUST_NO').agg(\n",
    "    # 交易笔数\n",
    "    ccd_tr_count=(amt_col, 'count'),\n",
    "    \n",
    "    # 金额统计\n",
    "    ccd_amt_sum=(amt_col, 'sum'),\n",
    "    ccd_amt_mean=(amt_col, 'mean'),\n",
    "    ccd_amt_std=(amt_col, 'std'),\n",
    "    ccd_amt_median=(amt_col, 'median'),\n",
    "    ccd_amt_max=(amt_col, 'max'),\n",
    "    ccd_amt_min=(amt_col, 'min'),\n",
    "    ccd_amt_skew=(amt_col, lambda x: x.skew()),\n",
    "    ccd_amt_kurt=(amt_col, lambda x: x.kurt()),\n",
    "    ccd_amt_q25=(amt_col, lambda x: x.quantile(0.25)),\n",
    "    ccd_amt_q75=(amt_col, lambda x: x.quantile(0.75)),\n",
    "    \n",
    "    # 活跃天数\n",
    "    active_days=('date_days_to_now', 'nunique'),\n",
    "    \n",
    "    # 工作日/周末交易\n",
    "    weekday_count=('is_weekend', lambda x: (x == 0).sum()),\n",
    "    weekend_count=('is_weekend', lambda x: (x == 1).sum()),\n",
    ").reset_index()\n",
    "\n",
    "# 派生特征\n",
    "ccd_basic_features['ccd_amt_range'] = ccd_basic_features['ccd_amt_max'] - ccd_basic_features['ccd_amt_min']\n",
    "ccd_basic_features['ccd_amt_cv'] = ccd_basic_features['ccd_amt_std'] / (ccd_basic_features['ccd_amt_mean'] + 1e-6)  # 变异系数\n",
    "ccd_basic_features['ccd_amt_per_trans'] = ccd_basic_features['ccd_amt_sum'] / (ccd_basic_features['ccd_tr_count'] + 1e-6)  # 笔均金额\n",
    "ccd_basic_features['ccd_trans_per_day'] = ccd_basic_features['ccd_tr_count'] / (ccd_basic_features['active_days'] + 1e-6)  # 日均笔数\n",
    "ccd_basic_features['weekend_ratio'] = ccd_basic_features['weekend_count'] / (ccd_basic_features['ccd_tr_count'] + 1e-6)  # 周末交易占比\n",
    "\n",
    "print(f\"✅ 基础消费统计特征生成完成，特征数: {len(ccd_basic_features.columns) - 1}\")\n",
    "ccd_basic_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9f040b4",
   "metadata": {},
   "source": [
    "### 步骤3: 时间序列特征（RFM中的R特征）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "59ef787d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成时间序列特征...\n",
      "✅ 时间序列特征生成完成，特征数: 9\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>last_tr_days_ago</th>\n",
       "      <th>first_tr_days_ago</th>\n",
       "      <th>tr_interval_mean</th>\n",
       "      <th>tr_interval_std</th>\n",
       "      <th>tr_interval_max</th>\n",
       "      <th>tr_interval_min</th>\n",
       "      <th>tr_interval_median</th>\n",
       "      <th>tr_interval_cv</th>\n",
       "      <th>tr_duration_days</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>064898a7d7d2bc34872e423170f97bfb</td>\n",
       "      <td>9</td>\n",
       "      <td>87</td>\n",
       "      <td>7.090909</td>\n",
       "      <td>8.300055</td>\n",
       "      <td>18.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.170520</td>\n",
       "      <td>78</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0b3a60d7823eacb570f33592bca838ad</td>\n",
       "      <td>64</td>\n",
       "      <td>87</td>\n",
       "      <td>2.875000</td>\n",
       "      <td>6.937218</td>\n",
       "      <td>20.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.5</td>\n",
       "      <td>2.412945</td>\n",
       "      <td>23</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2748b8bdd1889567b1755668e79b5ee7</td>\n",
       "      <td>8</td>\n",
       "      <td>87</td>\n",
       "      <td>7.181818</td>\n",
       "      <td>8.072400</td>\n",
       "      <td>19.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.124005</td>\n",
       "      <td>79</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>42c6b970935d3833c31276fe60aba663</td>\n",
       "      <td>6</td>\n",
       "      <td>28</td>\n",
       "      <td>5.500000</td>\n",
       "      <td>9.712535</td>\n",
       "      <td>20.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.765915</td>\n",
       "      <td>22</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4544d13ddc128e03327fe7ffac914765</td>\n",
       "      <td>0</td>\n",
       "      <td>87</td>\n",
       "      <td>9.666667</td>\n",
       "      <td>13.820275</td>\n",
       "      <td>30.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.429683</td>\n",
       "      <td>87</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  last_tr_days_ago  first_tr_days_ago  \\\n",
       "0  064898a7d7d2bc34872e423170f97bfb                 9                 87   \n",
       "1  0b3a60d7823eacb570f33592bca838ad                64                 87   \n",
       "2  2748b8bdd1889567b1755668e79b5ee7                 8                 87   \n",
       "3  42c6b970935d3833c31276fe60aba663                 6                 28   \n",
       "4  4544d13ddc128e03327fe7ffac914765                 0                 87   \n",
       "\n",
       "   tr_interval_mean  tr_interval_std  tr_interval_max  tr_interval_min  \\\n",
       "0          7.090909         8.300055             18.0              0.0   \n",
       "1          2.875000         6.937218             20.0              0.0   \n",
       "2          7.181818         8.072400             19.0              0.0   \n",
       "3          5.500000         9.712535             20.0              0.0   \n",
       "4          9.666667        13.820275             30.0              0.0   \n",
       "\n",
       "   tr_interval_median  tr_interval_cv  tr_duration_days  \n",
       "0                 0.0        1.170520                78  \n",
       "1                 0.5        2.412945                23  \n",
       "2                 1.0        1.124005                79  \n",
       "3                 1.0        1.765915                22  \n",
       "4                 1.0        1.429683                87  "
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 时间序列特征\n",
    "print(\"生成时间序列特征...\")\n",
    "\n",
    "# 按客户和日期排序\n",
    "ccd_time = ccd_tr_dtl.sort_values(['CUST_NO', 'date']).copy()\n",
    "\n",
    "# 计算交易间隔\n",
    "ccd_time['tr_interval'] = ccd_time.groupby('CUST_NO')['date'].diff().dt.days\n",
    "\n",
    "ccd_time_features = ccd_time.groupby('CUST_NO').agg(\n",
    "    # 最近一笔交易距今天数\n",
    "    last_tr_days_ago=('date_days_to_now', 'min'),\n",
    "    \n",
    "    # 首笔交易距今天数\n",
    "    first_tr_days_ago=('date_days_to_now', 'max'),\n",
    "    \n",
    "    # 交易间隔统计\n",
    "    tr_interval_mean=('tr_interval', 'mean'),\n",
    "    tr_interval_std=('tr_interval', 'std'),\n",
    "    tr_interval_max=('tr_interval', 'max'),\n",
    "    tr_interval_min=('tr_interval', 'min'),\n",
    "    tr_interval_median=('tr_interval', 'median'),\n",
    ").reset_index()\n",
    "\n",
    "# 派生特征\n",
    "ccd_time_features['tr_interval_cv'] = ccd_time_features['tr_interval_std'] / (ccd_time_features['tr_interval_mean'] + 1e-6)  # 间隔变异系数（规律性）\n",
    "ccd_time_features['tr_duration_days'] = ccd_time_features['first_tr_days_ago'] - ccd_time_features['last_tr_days_ago']  # 交易跨度天数\n",
    "\n",
    "print(f\"✅ 时间序列特征生成完成，特征数: {len(ccd_time_features.columns) - 1}\")\n",
    "ccd_time_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a49c734d",
   "metadata": {},
   "source": [
    "### 步骤4: 消费金额分布特征"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "dc0d3c9d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成消费金额分布特征...\n",
      "✅ 消费金额分布特征生成完成，特征数: 5\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>large_trans_count</th>\n",
       "      <th>large_trans_ratio</th>\n",
       "      <th>small_trans_count</th>\n",
       "      <th>small_trans_ratio</th>\n",
       "      <th>top3_amt_ratio</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>064898a7d7d2bc34872e423170f97bfb</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>3.0</td>\n",
       "      <td>0.250000</td>\n",
       "      <td>0.312498</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0b3a60d7823eacb570f33592bca838ad</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.111111</td>\n",
       "      <td>8.0</td>\n",
       "      <td>0.888889</td>\n",
       "      <td>0.999001</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2748b8bdd1889567b1755668e79b5ee7</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>3.0</td>\n",
       "      <td>0.250000</td>\n",
       "      <td>0.325521</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>42c6b970935d3833c31276fe60aba663</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>4.0</td>\n",
       "      <td>0.800000</td>\n",
       "      <td>0.976566</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4544d13ddc128e03327fe7ffac914765</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>4.0</td>\n",
       "      <td>0.400000</td>\n",
       "      <td>0.461538</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  large_trans_count  large_trans_ratio  \\\n",
       "0  064898a7d7d2bc34872e423170f97bfb                0.0           0.000000   \n",
       "1  0b3a60d7823eacb570f33592bca838ad                1.0           0.111111   \n",
       "2  2748b8bdd1889567b1755668e79b5ee7                0.0           0.000000   \n",
       "3  42c6b970935d3833c31276fe60aba663                0.0           0.000000   \n",
       "4  4544d13ddc128e03327fe7ffac914765                0.0           0.000000   \n",
       "\n",
       "   small_trans_count  small_trans_ratio  top3_amt_ratio  \n",
       "0                3.0           0.250000        0.312498  \n",
       "1                8.0           0.888889        0.999001  \n",
       "2                3.0           0.250000        0.325521  \n",
       "3                4.0           0.800000        0.976566  \n",
       "4                4.0           0.400000        0.461538  "
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 消费金额分布特征\n",
    "print(\"生成消费金额分布特征...\")\n",
    "\n",
    "# 大额/小额交易定义\n",
    "def amount_distribution_features(group):\n",
    "    \"\"\"计算金额分布相关特征\"\"\"\n",
    "    amt = group[amt_col]\n",
    "    mean_amt = amt.mean()\n",
    "    std_amt = amt.std()\n",
    "    \n",
    "    # 大额交易（>均值+2std）\n",
    "    large_threshold = mean_amt + 2 * std_amt\n",
    "    large_count = (amt > large_threshold).sum()\n",
    "    large_ratio = large_count / len(amt) if len(amt) > 0 else 0\n",
    "    \n",
    "    # 小额交易（<均值）\n",
    "    small_count = (amt < mean_amt).sum()\n",
    "    small_ratio = small_count / len(amt) if len(amt) > 0 else 0\n",
    "    \n",
    "    # Top3金额集中度\n",
    "    top3_sum = amt.nlargest(min(3, len(amt))).sum()\n",
    "    top3_ratio = top3_sum / amt.sum() if amt.sum() > 0 else 0\n",
    "    \n",
    "    return pd.Series({\n",
    "        'large_trans_count': large_count,\n",
    "        'large_trans_ratio': large_ratio,\n",
    "        'small_trans_count': small_count,\n",
    "        'small_trans_ratio': small_ratio,\n",
    "        'top3_amt_ratio': top3_ratio,\n",
    "    })\n",
    "\n",
    "ccd_dist_features = ccd_tr_dtl.groupby('CUST_NO').apply(amount_distribution_features).reset_index()\n",
    "\n",
    "print(f\"✅ 消费金额分布特征生成完成，特征数: {len(ccd_dist_features.columns) - 1}\")\n",
    "ccd_dist_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2315eac",
   "metadata": {},
   "source": [
    "### 步骤5: 月度趋势特征"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "634aa2b6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成月度趋势特征...\n",
      "✅ 月度趋势特征生成完成，特征数: 12\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>active_months</th>\n",
       "      <th>avg_month_count</th>\n",
       "      <th>month_count_std</th>\n",
       "      <th>month_count_max</th>\n",
       "      <th>month_count_min</th>\n",
       "      <th>avg_month_amt</th>\n",
       "      <th>month_amt_std</th>\n",
       "      <th>month_amt_max</th>\n",
       "      <th>month_amt_min</th>\n",
       "      <th>month_count_cv</th>\n",
       "      <th>month_amt_cv</th>\n",
       "      <th>amt_growth_rate</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>064898a7d7d2bc34872e423170f97bfb</td>\n",
       "      <td>3</td>\n",
       "      <td>4.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>4</td>\n",
       "      <td>4</td>\n",
       "      <td>9332.320000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>9332.32</td>\n",
       "      <td>9332.32</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0b3a60d7823eacb570f33592bca838ad</td>\n",
       "      <td>1</td>\n",
       "      <td>9.000000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>9</td>\n",
       "      <td>9</td>\n",
       "      <td>3004.000000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>3004.00</td>\n",
       "      <td>3004.00</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2748b8bdd1889567b1755668e79b5ee7</td>\n",
       "      <td>3</td>\n",
       "      <td>4.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>4</td>\n",
       "      <td>4</td>\n",
       "      <td>13824.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>13824.00</td>\n",
       "      <td>13824.00</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>42c6b970935d3833c31276fe60aba663</td>\n",
       "      <td>1</td>\n",
       "      <td>5.000000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>5</td>\n",
       "      <td>5</td>\n",
       "      <td>85332.320000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>85332.32</td>\n",
       "      <td>85332.32</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4544d13ddc128e03327fe7ffac914765</td>\n",
       "      <td>3</td>\n",
       "      <td>3.333333</td>\n",
       "      <td>0.57735</td>\n",
       "      <td>4</td>\n",
       "      <td>3</td>\n",
       "      <td>43.333333</td>\n",
       "      <td>0.57735</td>\n",
       "      <td>44.00</td>\n",
       "      <td>43.00</td>\n",
       "      <td>0.173205</td>\n",
       "      <td>0.013323</td>\n",
       "      <td>0.023256</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  active_months  avg_month_count  \\\n",
       "0  064898a7d7d2bc34872e423170f97bfb              3         4.000000   \n",
       "1  0b3a60d7823eacb570f33592bca838ad              1         9.000000   \n",
       "2  2748b8bdd1889567b1755668e79b5ee7              3         4.000000   \n",
       "3  42c6b970935d3833c31276fe60aba663              1         5.000000   \n",
       "4  4544d13ddc128e03327fe7ffac914765              3         3.333333   \n",
       "\n",
       "   month_count_std  month_count_max  month_count_min  avg_month_amt  \\\n",
       "0          0.00000                4                4    9332.320000   \n",
       "1              NaN                9                9    3004.000000   \n",
       "2          0.00000                4                4   13824.000000   \n",
       "3              NaN                5                5   85332.320000   \n",
       "4          0.57735                4                3      43.333333   \n",
       "\n",
       "   month_amt_std  month_amt_max  month_amt_min  month_count_cv  month_amt_cv  \\\n",
       "0        0.00000        9332.32        9332.32        0.000000      0.000000   \n",
       "1            NaN        3004.00        3004.00             NaN           NaN   \n",
       "2        0.00000       13824.00       13824.00        0.000000      0.000000   \n",
       "3            NaN       85332.32       85332.32             NaN           NaN   \n",
       "4        0.57735          44.00          43.00        0.173205      0.013323   \n",
       "\n",
       "   amt_growth_rate  \n",
       "0         0.000000  \n",
       "1              NaN  \n",
       "2         0.000000  \n",
       "3              NaN  \n",
       "4         0.023256  "
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 月度趋势特征\n",
    "print(\"生成月度趋势特征...\")\n",
    "\n",
    "# 按客户和月份聚合\n",
    "ccd_month_agg = ccd_tr_dtl.groupby(['CUST_NO', 'date_months_to_now']).agg(\n",
    "    month_count=(amt_col, 'count'),\n",
    "    month_amt_sum=(amt_col, 'sum'),\n",
    "    month_amt_mean=(amt_col, 'mean'),\n",
    ").reset_index()\n",
    "\n",
    "# 跨月特征\n",
    "ccd_month_features = ccd_month_agg.groupby('CUST_NO').agg(\n",
    "    # 活跃月份数\n",
    "    active_months=('date_months_to_now', 'nunique'),\n",
    "    \n",
    "    # 月均交易笔数\n",
    "    avg_month_count=('month_count', 'mean'),\n",
    "    month_count_std=('month_count', 'std'),\n",
    "    month_count_max=('month_count', 'max'),\n",
    "    month_count_min=('month_count', 'min'),\n",
    "    \n",
    "    # 月均交易金额\n",
    "    avg_month_amt=('month_amt_sum', 'mean'),\n",
    "    month_amt_std=('month_amt_sum', 'std'),\n",
    "    month_amt_max=('month_amt_sum', 'max'),\n",
    "    month_amt_min=('month_amt_sum', 'min'),\n",
    ").reset_index()\n",
    "\n",
    "# 月度稳定性\n",
    "ccd_month_features['month_count_cv'] = ccd_month_features['month_count_std'] / (ccd_month_features['avg_month_count'] + 1e-6)\n",
    "ccd_month_features['month_amt_cv'] = ccd_month_features['month_amt_std'] / (ccd_month_features['avg_month_amt'] + 1e-6)\n",
    "\n",
    "# 计算月度增长率（最近1个月vs最近2个月）\n",
    "recent_months = ccd_month_agg[ccd_month_agg['date_months_to_now'].isin([0, 1])]\n",
    "if len(recent_months) > 0:\n",
    "    month_growth = recent_months.pivot_table(index='CUST_NO', columns='date_months_to_now', values='month_amt_sum', aggfunc='sum')\n",
    "    if 0 in month_growth.columns and 1 in month_growth.columns:\n",
    "        month_growth['amt_growth_rate'] = (month_growth[0] - month_growth[1]) / (month_growth[1] + 1e-6)\n",
    "        month_growth = month_growth[['amt_growth_rate']].reset_index()\n",
    "        ccd_month_features = ccd_month_features.merge(month_growth, on='CUST_NO', how='left')\n",
    "\n",
    "print(f\"✅ 月度趋势特征生成完成，特征数: {len(ccd_month_features.columns) - 1}\")\n",
    "ccd_month_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29a89942",
   "metadata": {},
   "source": [
    "### 步骤6: 商户/交易类型维度特征（如果有相关字段）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "cc0d882c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成商户/交易类型维度特征...\n",
      "使用类别字段: ['COD_PSG', 'COD_TR']\n",
      "✅ 商户/交易类型特征生成完成，特征数: 2\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>category_count</th>\n",
       "      <th>main_category_ratio</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>064898a7d7d2bc34872e423170f97bfb</td>\n",
       "      <td>2</td>\n",
       "      <td>0.750000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0b3a60d7823eacb570f33592bca838ad</td>\n",
       "      <td>2</td>\n",
       "      <td>0.888889</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2748b8bdd1889567b1755668e79b5ee7</td>\n",
       "      <td>2</td>\n",
       "      <td>0.750000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>42c6b970935d3833c31276fe60aba663</td>\n",
       "      <td>3</td>\n",
       "      <td>0.600000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4544d13ddc128e03327fe7ffac914765</td>\n",
       "      <td>2</td>\n",
       "      <td>0.900000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  category_count  main_category_ratio\n",
       "0  064898a7d7d2bc34872e423170f97bfb               2             0.750000\n",
       "1  0b3a60d7823eacb570f33592bca838ad               2             0.888889\n",
       "2  2748b8bdd1889567b1755668e79b5ee7               2             0.750000\n",
       "3  42c6b970935d3833c31276fe60aba663               3             0.600000\n",
       "4  4544d13ddc128e03327fe7ffac914765               2             0.900000"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 商户/交易类型维度特征\n",
    "print(\"生成商户/交易类型维度特征...\")\n",
    "\n",
    "# COD_PSG是通道代码, COD_TR是交易代码\n",
    "category_cols = ['COD_PSG', 'COD_TR']\n",
    "print(f\"使用类别字段: {category_cols}\")\n",
    "\n",
    "if len(category_cols) > 0:\n",
    "    # 使用第一个类别字段作为示例\n",
    "    cat_col = category_cols[0]\n",
    "    \n",
    "    # 类别多样性\n",
    "    def category_concentration(x):\n",
    "        \"\"\"计算类别集中度\"\"\"\n",
    "        if len(x) == 0:\n",
    "            return 0\n",
    "        return x.value_counts().iloc[0] / len(x)\n",
    "    \n",
    "    ccd_category_features = ccd_tr_dtl.groupby('CUST_NO').agg(\n",
    "        category_count=(cat_col, 'nunique'),  # 类别多样性\n",
    "        main_category_ratio=(cat_col, category_concentration),  # 主类别占比\n",
    "    ).reset_index()\n",
    "    \n",
    "    print(f\"✅ 商户/交易类型特征生成完成，特征数: {len(ccd_category_features.columns) - 1}\")\n",
    "else:\n",
    "    # 如果没有类别字段，创建空特征\n",
    "    ccd_category_features = ccd_tr_dtl[['CUST_NO']].drop_duplicates().reset_index(drop=True)\n",
    "    print(\"⚠️  未检测到商户/交易类型字段，跳过此类特征\")\n",
    "\n",
    "ccd_category_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98af901a",
   "metadata": {},
   "source": [
    "### 步骤7: 合并信用卡交易所有特征并保存"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "1313af0b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "合并所有信用卡交易特征...\n",
      "\n",
      "============================================================\n",
      "💳 信用卡交易特征工程总结\n",
      "============================================================\n",
      "✅ 特征总数: 47\n",
      "✅ 客户数: 18\n",
      "✅ 特征列表:\n",
      "   1. ccd_tr_count\n",
      "   2. ccd_amt_sum\n",
      "   3. ccd_amt_mean\n",
      "   4. ccd_amt_std\n",
      "   5. ccd_amt_median\n",
      "   6. ccd_amt_max\n",
      "   7. ccd_amt_min\n",
      "   8. ccd_amt_skew\n",
      "   9. ccd_amt_kurt\n",
      "   10. ccd_amt_q25\n",
      "   11. ccd_amt_q75\n",
      "   12. active_days\n",
      "   13. weekday_count\n",
      "   14. weekend_count\n",
      "   15. ccd_amt_range\n",
      "   16. ccd_amt_cv\n",
      "   17. ccd_amt_per_trans\n",
      "   18. ccd_trans_per_day\n",
      "   19. weekend_ratio\n",
      "   20. last_tr_days_ago\n",
      "   21. first_tr_days_ago\n",
      "   22. tr_interval_mean\n",
      "   23. tr_interval_std\n",
      "   24. tr_interval_max\n",
      "   25. tr_interval_min\n",
      "   26. tr_interval_median\n",
      "   27. tr_interval_cv\n",
      "   28. tr_duration_days\n",
      "   29. large_trans_count\n",
      "   30. large_trans_ratio\n",
      "   31. small_trans_count\n",
      "   32. small_trans_ratio\n",
      "   33. top3_amt_ratio\n",
      "   34. active_months\n",
      "   35. avg_month_count\n",
      "   36. month_count_std\n",
      "   37. month_count_max\n",
      "   38. month_count_min\n",
      "   39. avg_month_amt\n",
      "   40. month_amt_std\n",
      "   41. month_amt_max\n",
      "   42. month_amt_min\n",
      "   43. month_count_cv\n",
      "   44. month_amt_cv\n",
      "   45. amt_growth_rate\n",
      "   46. category_count\n",
      "   47. main_category_ratio\n",
      "\n",
      "✅ 信用卡交易特征已保存至: ./feature/CCD_TR_DTL_features.pkl\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>ccd_tr_count</th>\n",
       "      <th>ccd_amt_sum</th>\n",
       "      <th>ccd_amt_mean</th>\n",
       "      <th>ccd_amt_std</th>\n",
       "      <th>ccd_amt_median</th>\n",
       "      <th>ccd_amt_max</th>\n",
       "      <th>ccd_amt_min</th>\n",
       "      <th>ccd_amt_skew</th>\n",
       "      <th>ccd_amt_kurt</th>\n",
       "      <th>...</th>\n",
       "      <th>month_count_min</th>\n",
       "      <th>avg_month_amt</th>\n",
       "      <th>month_amt_std</th>\n",
       "      <th>month_amt_max</th>\n",
       "      <th>month_amt_min</th>\n",
       "      <th>month_count_cv</th>\n",
       "      <th>month_amt_cv</th>\n",
       "      <th>amt_growth_rate</th>\n",
       "      <th>category_count</th>\n",
       "      <th>main_category_ratio</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>064898a7d7d2bc34872e423170f97bfb</td>\n",
       "      <td>12</td>\n",
       "      <td>27996.96</td>\n",
       "      <td>2333.080000</td>\n",
       "      <td>1055.138950</td>\n",
       "      <td>2916.33</td>\n",
       "      <td>2916.33</td>\n",
       "      <td>583.33</td>\n",
       "      <td>-1.326650</td>\n",
       "      <td>-0.325926</td>\n",
       "      <td>...</td>\n",
       "      <td>4</td>\n",
       "      <td>9332.320000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>9332.32</td>\n",
       "      <td>9332.32</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>2</td>\n",
       "      <td>0.750000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0b3a60d7823eacb570f33592bca838ad</td>\n",
       "      <td>9</td>\n",
       "      <td>3004.00</td>\n",
       "      <td>333.777778</td>\n",
       "      <td>999.833333</td>\n",
       "      <td>0.50</td>\n",
       "      <td>3000.00</td>\n",
       "      <td>0.50</td>\n",
       "      <td>3.000000</td>\n",
       "      <td>9.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>9</td>\n",
       "      <td>3004.000000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>3004.00</td>\n",
       "      <td>3004.00</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>2</td>\n",
       "      <td>0.888889</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2748b8bdd1889567b1755668e79b5ee7</td>\n",
       "      <td>12</td>\n",
       "      <td>41472.00</td>\n",
       "      <td>3456.000000</td>\n",
       "      <td>1885.049360</td>\n",
       "      <td>4497.00</td>\n",
       "      <td>4500.00</td>\n",
       "      <td>330.00</td>\n",
       "      <td>-1.326648</td>\n",
       "      <td>-0.325928</td>\n",
       "      <td>...</td>\n",
       "      <td>4</td>\n",
       "      <td>13824.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>13824.00</td>\n",
       "      <td>13824.00</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>2</td>\n",
       "      <td>0.750000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>42c6b970935d3833c31276fe60aba663</td>\n",
       "      <td>5</td>\n",
       "      <td>85332.32</td>\n",
       "      <td>17066.464000</td>\n",
       "      <td>35185.650870</td>\n",
       "      <td>1666.33</td>\n",
       "      <td>80000.00</td>\n",
       "      <td>333.33</td>\n",
       "      <td>2.234555</td>\n",
       "      <td>4.994665</td>\n",
       "      <td>...</td>\n",
       "      <td>5</td>\n",
       "      <td>85332.320000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>85332.32</td>\n",
       "      <td>85332.32</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>3</td>\n",
       "      <td>0.600000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4544d13ddc128e03327fe7ffac914765</td>\n",
       "      <td>10</td>\n",
       "      <td>130.00</td>\n",
       "      <td>13.000000</td>\n",
       "      <td>9.055385</td>\n",
       "      <td>20.00</td>\n",
       "      <td>20.00</td>\n",
       "      <td>1.00</td>\n",
       "      <td>-0.499411</td>\n",
       "      <td>-2.224845</td>\n",
       "      <td>...</td>\n",
       "      <td>3</td>\n",
       "      <td>43.333333</td>\n",
       "      <td>0.57735</td>\n",
       "      <td>44.00</td>\n",
       "      <td>43.00</td>\n",
       "      <td>0.173205</td>\n",
       "      <td>0.013323</td>\n",
       "      <td>0.023256</td>\n",
       "      <td>2</td>\n",
       "      <td>0.900000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 48 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  ccd_tr_count  ccd_amt_sum  ccd_amt_mean  \\\n",
       "0  064898a7d7d2bc34872e423170f97bfb            12     27996.96   2333.080000   \n",
       "1  0b3a60d7823eacb570f33592bca838ad             9      3004.00    333.777778   \n",
       "2  2748b8bdd1889567b1755668e79b5ee7            12     41472.00   3456.000000   \n",
       "3  42c6b970935d3833c31276fe60aba663             5     85332.32  17066.464000   \n",
       "4  4544d13ddc128e03327fe7ffac914765            10       130.00     13.000000   \n",
       "\n",
       "    ccd_amt_std  ccd_amt_median  ccd_amt_max  ccd_amt_min  ccd_amt_skew  \\\n",
       "0   1055.138950         2916.33      2916.33       583.33     -1.326650   \n",
       "1    999.833333            0.50      3000.00         0.50      3.000000   \n",
       "2   1885.049360         4497.00      4500.00       330.00     -1.326648   \n",
       "3  35185.650870         1666.33     80000.00       333.33      2.234555   \n",
       "4      9.055385           20.00        20.00         1.00     -0.499411   \n",
       "\n",
       "   ccd_amt_kurt  ...  month_count_min  avg_month_amt  month_amt_std  \\\n",
       "0     -0.325926  ...                4    9332.320000        0.00000   \n",
       "1      9.000000  ...                9    3004.000000            NaN   \n",
       "2     -0.325928  ...                4   13824.000000        0.00000   \n",
       "3      4.994665  ...                5   85332.320000            NaN   \n",
       "4     -2.224845  ...                3      43.333333        0.57735   \n",
       "\n",
       "   month_amt_max  month_amt_min  month_count_cv  month_amt_cv  \\\n",
       "0        9332.32        9332.32        0.000000      0.000000   \n",
       "1        3004.00        3004.00             NaN           NaN   \n",
       "2       13824.00       13824.00        0.000000      0.000000   \n",
       "3       85332.32       85332.32             NaN           NaN   \n",
       "4          44.00          43.00        0.173205      0.013323   \n",
       "\n",
       "   amt_growth_rate  category_count  main_category_ratio  \n",
       "0         0.000000               2             0.750000  \n",
       "1              NaN               2             0.888889  \n",
       "2         0.000000               2             0.750000  \n",
       "3              NaN               3             0.600000  \n",
       "4         0.023256               2             0.900000  \n",
       "\n",
       "[5 rows x 48 columns]"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 合并所有信用卡交易特征\n",
    "print(\"合并所有信用卡交易特征...\")\n",
    "\n",
    "# 逐步合并\n",
    "ccd_features_final = ccd_basic_features.copy()\n",
    "ccd_features_final = ccd_features_final.merge(ccd_time_features, on='CUST_NO', how='left')\n",
    "ccd_features_final = ccd_features_final.merge(ccd_dist_features, on='CUST_NO', how='left')\n",
    "ccd_features_final = ccd_features_final.merge(ccd_month_features, on='CUST_NO', how='left')\n",
    "ccd_features_final = ccd_features_final.merge(ccd_category_features, on='CUST_NO', how='left')\n",
    "\n",
    "print(f\"\\n{'='*60}\")\n",
    "print(f\"💳 信用卡交易特征工程总结\")\n",
    "print(f\"{'='*60}\")\n",
    "print(f\"✅ 特征总数: {len(ccd_features_final.columns) - 1}\")\n",
    "print(f\"✅ 客户数: {len(ccd_features_final)}\")\n",
    "print(f\"✅ 特征列表:\")\n",
    "for i, col in enumerate(ccd_features_final.columns):\n",
    "    if col != 'CUST_NO':\n",
    "        print(f\"   {i}. {col}\")\n",
    "\n",
    "# 保存特征到pickle文件\n",
    "feature_path = './feature/CCD_TR_DTL_features.pkl'\n",
    "with open(feature_path, 'wb') as f:\n",
    "    pickle.dump(ccd_features_final, f)\n",
    "\n",
    "print(f\"\\n✅ 信用卡交易特征已保存至: {feature_path}\")\n",
    "ccd_features_final.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a1b8446",
   "metadata": {},
   "source": [
    "## 📝 保存特征名称列表"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "03846e52",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "保存特征名称列表...\n",
      "✅ 代发工资特征名称已保存，共 46 个特征\n",
      "✅ 信用卡交易特征名称已保存，共 47 个特征\n",
      "\n",
      "============================================================\n",
      "🎉 特征工程全部完成！\n",
      "============================================================\n",
      "📁 生成的文件:\n",
      "   1. ./feature/AGET_PAY_features.pkl - 代发工资特征数据\n",
      "   2. ./feature/AGET_PAY_feature_names.txt - 代发工资特征名称列表\n",
      "   3. ./feature/CCD_TR_DTL_features.pkl - 信用卡交易特征数据\n",
      "   4. ./feature/CCD_TR_DTL_feature_names.txt - 信用卡交易特征名称列表\n",
      "\n",
      "💡 特征统计:\n",
      "   - 代发工资特征: 46 个\n",
      "   - 信用卡交易特征: 47 个\n",
      "   - 总特征数: 93 个\n"
     ]
    }
   ],
   "source": [
    "# 保存特征名称列表（用于后续建模）\n",
    "print(\"保存特征名称列表...\")\n",
    "\n",
    "# 代发工资特征名称\n",
    "aget_pay_feature_names = [col for col in aget_pay_features_final.columns if col != 'CUST_NO']\n",
    "with open('./feature/AGET_PAY_feature_names.txt', 'w', encoding='utf-8') as f:\n",
    "    for name in aget_pay_feature_names:\n",
    "        f.write(f\"{name}\\n\")\n",
    "print(f\"✅ 代发工资特征名称已保存，共 {len(aget_pay_feature_names)} 个特征\")\n",
    "\n",
    "# 信用卡交易特征名称\n",
    "ccd_feature_names = [col for col in ccd_features_final.columns if col != 'CUST_NO']\n",
    "with open('./feature/CCD_TR_DTL_feature_names.txt', 'w', encoding='utf-8') as f:\n",
    "    for name in ccd_feature_names:\n",
    "        f.write(f\"{name}\\n\")\n",
    "print(f\"✅ 信用卡交易特征名称已保存，共 {len(ccd_feature_names)} 个特征\")\n",
    "\n",
    "print(f\"\\n{'='*60}\")\n",
    "print(f\"🎉 特征工程全部完成！\")\n",
    "print(f\"{'='*60}\")\n",
    "print(f\"📁 生成的文件:\")\n",
    "print(f\"   1. ./feature/AGET_PAY_features.pkl - 代发工资特征数据\")\n",
    "print(f\"   2. ./feature/AGET_PAY_feature_names.txt - 代发工资特征名称列表\")\n",
    "print(f\"   3. ./feature/CCD_TR_DTL_features.pkl - 信用卡交易特征数据\")\n",
    "print(f\"   4. ./feature/CCD_TR_DTL_feature_names.txt - 信用卡交易特征名称列表\")\n",
    "print(f\"\\n💡 特征统计:\")\n",
    "print(f\"   - 代发工资特征: {len(aget_pay_feature_names)} 个\")\n",
    "print(f\"   - 信用卡交易特征: {len(ccd_feature_names)} 个\")\n",
    "print(f\"   - 总特征数: {len(aget_pay_feature_names) + len(ccd_feature_names)} 个\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ca1a815",
   "metadata": {},
   "source": [
    "## 🚀 高级特征工程 - 增强版\n",
    "\n",
    "现在添加更多高级特征来提升模型性能"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a7bcebe",
   "metadata": {},
   "source": [
    "### 代发工资高级特征"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c6093b4",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 📋 特征工程完整总结\n",
    "\n",
    "### 一、代发工资信息表(AGET_PAY)特征体系\n",
    "\n",
    "#### 1. 基础统计特征 (13个)\n",
    "- **笔数特征**: 代发总笔数、代发单位数\n",
    "- **金额特征**: 总和、均值、标准差、中位数、最大值、最小值、偏度、峰度\n",
    "- **派生特征**: 金额范围、变异系数、单位平均金额\n",
    "\n",
    "#### 2. 时间序列特征 (9个)\n",
    "- **间隔统计**: 最大间隔、最小间隔、平均间隔、间隔标准差、间隔中位数\n",
    "- **最近性**: 最近一次代发距今天数、首次代发距今天数\n",
    "- **派生特征**: 间隔变异系数(规律性指标)、间隔范围\n",
    "\n",
    "#### 3. 单位维度特征 (4个)\n",
    "- **集中度**: 主要单位占比、主要单位类型占比、主要省份占比\n",
    "- **金额集中**: 最大代发单位金额占比\n",
    "\n",
    "#### 4. 月度特征 (13个)\n",
    "- **月度统计**: 活跃月份数、月均代发次数及标准差\n",
    "- **金额趋势**: 月均代发金额、月度金额标准差、最大最小值\n",
    "- **单位统计**: 月均单位数、月度单位标准差\n",
    "- **稳定性**: 月度笔数变异系数、月度金额变异系数\n",
    "\n",
    "#### 5. 类别特征 (4个)\n",
    "- 省份编码统计(均值、种类数)\n",
    "- 单位类型编码统计(均值、种类数)\n",
    "\n",
    "**代发工资特征总数: 约43个**\n",
    "\n",
    "---\n",
    "\n",
    "### 二、信用卡交易流水表(CCD_TR_DTL)特征体系\n",
    "\n",
    "#### 1. 基础消费统计特征 (20个)\n",
    "- **笔数特征**: 交易总笔数、活跃天数、工作日笔数、周末笔数\n",
    "- **金额特征**: 总和、均值、标准差、中位数、最大值、最小值、偏度、峰度、25/75分位数\n",
    "- **派生特征**: 金额范围、变异系数、笔均金额、日均笔数、周末交易占比\n",
    "\n",
    "#### 2. 时间序列特征 (10个)\n",
    "- **最近性**: 最近一笔交易距今天数、首笔交易距今天数\n",
    "- **间隔统计**: 平均间隔、间隔标准差、最大最小间隔、中位数间隔\n",
    "- **派生特征**: 间隔变异系数、交易跨度天数\n",
    "\n",
    "#### 3. 消费金额分布特征 (5个)\n",
    "- **异常检测**: 大额交易笔数及占比(>均值+2std)、小额交易笔数及占比(<均值)\n",
    "- **集中度**: Top3金额集中度\n",
    "\n",
    "#### 4. 月度趋势特征 (11+个)\n",
    "- **月度统计**: 活跃月份数、月均交易笔数及标准差\n",
    "- **金额趋势**: 月均交易金额、月度金额标准差、最大最小值\n",
    "- **稳定性**: 月度笔数变异系数、月度金额变异系数\n",
    "- **增长率**: 近期月度金额环比增长率\n",
    "\n",
    "#### 5. 商户/交易类型特征 (2+个，根据实际字段)\n",
    "- 商户类型多样性(nunique)\n",
    "- 主商户类型集中度\n",
    "\n",
    "**信用卡交易特征总数: 约48个**\n",
    "\n",
    "---\n",
    "\n",
    "### 三、特征工程核心方法论\n",
    "\n",
    "#### 1. RFM分析框架\n",
    "- **R (Recency)**: 最近性特征 - 捕捉用户活跃度\n",
    "- **F (Frequency)**: 频率特征 - 捕捉用户粘性\n",
    "- **M (Monetary)**: 金额特征 - 捕捉用户价值\n",
    "\n",
    "#### 2. 统计分布特征\n",
    "- **集中趋势**: mean, median\n",
    "- **离散程度**: std, cv (变异系数)\n",
    "- **分布形态**: skew (偏度), kurt (峰度)\n",
    "- **分位数**: 四分位数捕捉分布细节\n",
    "\n",
    "#### 3. 时间序列特征\n",
    "- **趋势特征**: 月度环比增长率\n",
    "- **稳定性**: 间隔变异系数、月度CV\n",
    "- **周期性**: 工作日/周末差异\n",
    "\n",
    "#### 4. 集中度特征\n",
    "- **Top-N集中**: Top3占比\n",
    "- **主体占比**: 主要单位/商户占比\n",
    "- **Shannon熵**: 多样性度量\n",
    "\n",
    "#### 5. 交叉维度特征\n",
    "- **类别分组**: 按月、按单位类型、按商户类型\n",
    "- **透视转换**: 将类别特征展开为多列\n",
    "\n",
    "---\n",
    "\n",
    "### 四、参考方案来源\n",
    "\n",
    "1. **2023年公私联动第一名方案** - 代发工资特征设计\n",
    "   - 单位类型编码策略\n",
    "   - 代发间隔规律性分析\n",
    "   - 跨月稳定性特征\n",
    "\n",
    "2. **2024年营销响应第一名方案** - 信用卡消费特征设计\n",
    "   - 消费行为模式分析\n",
    "   - 金额分布异常检测\n",
    "   - 月度趋势变化特征\n",
    "\n",
    "3. **2023年诈骗预测第一名方案** - 活期交易特征工程\n",
    "   - RFM特征体系\n",
    "   - 流入流出分组处理\n",
    "   - 渠道偏好特征\n",
    "\n",
    "---\n",
    "\n",
    "### 五、特征使用建议\n",
    "\n",
    "#### 1. 特征筛选\n",
    "- 建议使用LightGBM的feature_importance进行特征重要性评估\n",
    "- 可以使用基于方差的过滤去除低方差特征\n",
    "- 注意多重共线性问题，使用VIF检测\n",
    "\n",
    "#### 2. 特征组合\n",
    "- 可以与其他表的特征(资产、产品持有等)进行merge\n",
    "- 考虑构建代发工资与信用卡消费的比值特征\n",
    "- 探索收入支出比等财务健康度指标\n",
    "\n",
    "#### 3. 模型应用\n",
    "- 适用于分类任务(产品推荐、风险预测)\n",
    "- 适用于回归任务(额度预测、消费预测)\n",
    "- 支持树模型(LightGBM, XGBoost, CatBoost)\n",
    "- 需要标准化后才能用于神经网络\n",
    "\n",
    "---\n",
    "\n",
    "**特征工程完成时间**: 2025年10月17日  \n",
    "**特征总数**: 约91个 (代发43个 + 信用卡48个)  \n",
    "**特征存储格式**: pickle  \n",
    "**特征存储路径**: ./feature/"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "starcup",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
